Plot1(a): Trend on people who tested positive

#Calculate rolling average for Health board equals Scotland   
trend_hb_daily_roll_avg <- calculate_roll_avg("positive")

#convert the data frame to time series to use the range slider and selector
trend_hb_daily_roll_avg_ts <- convert_to_timeseries(trend_hb_daily_roll_avg)

# Create the plot using plot_ly
trend_positive_plot <- plot_ly(trend_hb_daily_roll_avg_ts, x = ~Date,
                          hoverinfo ="text",
                          text = ~paste("Date: ", Date,"<br>",
                                        "Daily Positive: ", daily_positive,"<br>",
                                        "Seven Day Average: ",seven_day_roll_avg))  
#add bar chart for daily positive
trend_positive_plot <- trend_positive_plot %>% add_bars(y = ~daily_positive, 
                                              name = "Daily Positive",
                                              color  =  I("#5ab4ac")  
                                              )
#add line for rolling average
trend_positive_plot <- trend_positive_plot %>% add_lines(y = ~seven_day_roll_avg,
                                               name = "Seven Day Rolling Average",
                                               color = I("red")) 
#add layout to the existing plot
trend_positive_plot <- trend_positive_plot %>% layout(
    title = list(text ="Trend on Positive Cases"),
    xaxis = xaxis_list,
    yaxis = yaxis_list,
    legend = list(title=list(text='<b> Trend </b>'),
                  x = 0.1, y = 0.9))
     
trend_positive_plot
NA

Data for the report (Presentation)

total_positive_case <- trend_hb_daily %>% 
  filter(hb_name == "Scotland") %>% 
  filter(date == max(date))

recent_7days <- calculate_roll_avg("positive") %>% 
  filter (date >= max(date)-7) %>% 
  summarise(total = sum(daily_positive))

previous_7days <- calculate_roll_avg("positive") %>% 
  filter (date >= max(date)-14) %>% 
  filter (date < max(date)-7) %>% 
  summarise(total = sum(daily_positive))

percentage_calc = (recent_7days -previous_7days )/previous_7days * 100 

percentage_calc
# decrease of 13.04 %

total_hospital <- trend_hb_daily %>% 
  filter(hb_name == "Scotland") %>% 
  summarise(total = sum(hospital_admissions, na.rm = TRUE))

recent_7days_hospital <- calculate_roll_avg("hospital") %>% 
  filter (!(is.na(seven_day_roll_avg))) %>% 
  filter (date >= max(date)-7) %>% 
  summarise(total = sum(hospital_admissions))

previous_7days_hospital <- calculate_roll_avg("hospital") %>% 
  filter (!(is.na(seven_day_roll_avg))) %>% 
  filter (date >= max(date)-14) %>% 
  filter (date < max(date)-7) %>% 
  summarise(total = sum(hospital_admissions))

percentage_calc_hosp = (recent_7days_hospital -previous_7days_hospital )/previous_7days_hospital * 100 

percentage_calc_hosp
# decrease of 15.11 %


icu_hospital <- trend_hb_daily %>% 
  filter(hb_name == "Scotland") %>% 
  summarise(total = sum(icu_admissions, na.rm = TRUE))

cum_deaths <- trend_hb_daily %>% 
  filter(hb_name == "Scotland") %>% 
  filter (date == max(date)) %>% 
  select (cumulative_deaths)

daily_vacc_hb %>% 
  filter(hb_name == "Scotland",
         sex == "Total",
         age_group == "All vaccinations") %>% 
  filter (dose == "Dose 1") %>% 
  filter (date == max(date))

Plot1(b): Total Positive Case by neighborhood (Based on Local Authority).


trend_la_daily_nb <- calculate_roll_avg("la_neighbour")

# Get the population data  
la_population <-trend_seven_day %>% 
  filter(date == max(date)) %>% 
  group_by (ca) %>% 
  summarise(total_population = sum(population))

# Prepare the map data by combining spatial data and (local authority) la data  
map_data <- zones_la %>% 
    inner_join(trend_la_daily_nb, by = c("code" = "ca"))%>% 
    inner_join(la_population, by = c("code" = "ca")) %>% 
  arrange(seven_day_roll_avg)

# Create a map palette  
map_palette <- colorNumeric("plasma", domain = range(map_data$seven_day_roll_avg))
map_data <- map_data %>% 
    mutate(colour = map_palette(seven_day_roll_avg))

# Display the spatial data
map_data %>%
    leaflet() %>%
    addPolygons(
     popup = ~ str_c("<b><h2>",name,  "</h2>", 
                      "<h3>(01-Oct-2011 to 07-Oct-2021)</h3></b>", 
                      "<b>Number of Positive Cases over 7 days: </b>", seven_day_roll_avg,
                     " <br><b>7 day rate per 100,000 people: </b>", crude_rate7day_positive,
                     " <br><br><b>Total Population: </b>", total_population,
                     
                      sep = ""),
      color = ~colour
    ) %>%
    addLegend(
      position = "topright",
      colors = ~colour,
      labels = ~name
    )
NA

2 Analyse the trend on Hospitalizations:

#Calculate rolling average for Health board equals Scotland   
trend_hb_hosp_roll_avg <- calculate_roll_avg("hospital")

#convert the data frame to time series to use the range slider and selector
trend_hb_hosp_roll_avg_ts <- convert_to_timeseries(trend_hb_hosp_roll_avg)

# Create the plot using plot_ly
trend_hospital_plot <- plot_ly(trend_hb_hosp_roll_avg_ts, x = ~Date,
                          hoverinfo ="text",
                          text = ~paste("Date: ", Date,"<br>",
                                        "Patients admitted: ", hospital_admissions,"<br>",
                                        "Seven Day Average: ",seven_day_roll_avg))  
#add bar chart for daily positive
trend_hospital_plot <- trend_hospital_plot %>% add_bars(y = ~hospital_admissions, 
                                              name = "Patients admitted",
                                              color  =  I("#5ab4ac")  
                                              )
#add line for rolling average
trend_hospital_plot <- trend_hospital_plot %>% add_lines(y = ~seven_day_roll_avg,
                                               name = "Seven Day Rolling Average",
                                               color = I("red")) 
#add layout to the existing plot
trend_hospital_plot <- trend_hospital_plot %>% layout(
    title = list(text ="Hospitalisation"),
    xaxis = xaxis_list,
    yaxis = yaxis_list,
    legend = list(title=list(text='<b> Trend </b>'),
                  x = 0.1, y = 0.9))
     
trend_hospital_plot
Warning: Ignoring 2 observations
Warning: Ignoring 2 observations

2 Analyse the trend on Deaths:

#Calculate rolling average for Health board equals Scotland   
trend_hb_death_roll_avg <- calculate_roll_avg("death")

#convert the data frame to time series to use the range slider and selector
trend_hb_death_roll_avg_ts <- convert_to_timeseries(trend_hb_death_roll_avg)

# Create the plot using plot_ly
trend_death_plot <- plot_ly(trend_hb_death_roll_avg_ts, x = ~Date,
                          hoverinfo ="text",
                          text = ~paste("Date: ", Date,"<br>",
                                        "Daily Deaths: ", daily_deaths,"<br>",
                                        "Seven Day Average: ",seven_day_roll_avg))  
#add bar chart for daily positive
trend_death_plot <- trend_death_plot %>% add_bars(y = ~daily_deaths, 
                                              name = "Daily Deaths",
                                              color  =  I("#5ab4ac")  
                                              )
#add line for rolling average
trend_death_plot <- trend_death_plot %>% add_lines(y = ~seven_day_roll_avg,
                                               name = "Seven Day Rolling Average",
                                               color = I("red")) 
#add layout to the existing plot
trend_death_plot <- trend_death_plot %>% layout(
    title = list(text ="Deaths reported"),
    xaxis = xaxis_list,
    yaxis = yaxis_list,
    legend = list(title=list(text='<b> Trend </b>'),
                  x = 0.1, y = 0.9))
     
trend_death_plot
NA

“PHS_COVID Vaccination Prediction Model Using ARIMA”

Data Preparation


trend_vacc_hb <- daily_vacc_hb %>% 
  filter(hb_name == "Scotland") %>% 
  filter(sex =="Total") %>% 
  filter(age_group == "All vaccinations") %>% 
  filter(cumulative_number_vaccinated!=0) 

Analyse the trend on Vaccinations:

Plot1(a): Trend on Vaccination

#Plot to visualize trend on vaccination.
plot_vaccine <- trend_vacc_hb %>% 
  ggplot()+
  aes(x = date, y = number_vaccinated)+
  geom_line(aes(color = dose))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("People Vaccinated") +
  xlab("Year") +
  ylab("No of Positive Cases") +
  color_theme()+
  scale_colour_manual(values = c("#f1a340", "#5ab4ac"))

ggplotly(plot_vaccine)

Plot1(b): Cumulative Total on Vaccination

# Identify the population
dose_population <- daily_vacc_hb %>% 
  filter(sex == "Total") %>% 
  filter(date == max(date)) %>% 
  filter(hb_name =="Scotland") %>% 
  filter(age_group =="16 years and over") %>% 
  select(population) %>% 
  distinct()

#Plot to visualise cumulative vaccination trend.  
plot_vaccine_cumm <- trend_vacc_hb %>% 
  ggplot()+
  aes(x = date, y = cumulative_number_vaccinated)+
  geom_line(aes(color = dose))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Cummulative Trend on Vaccination") +
  xlab("Year") +
  ylab("No of People Vaccinated") +
  color_theme()+
  scale_colour_manual(values = c("#f1a340", "#5ab4ac"))+
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6), 
                     sec.axis = sec_axis(trans = ~./dose_population$population,
                     name = "Percentage",
                     labels = scales::label_percent(accuracy = 0.01)
                    ))

plotly::ggplotly(plot_vaccine_cumm)
NA

Plot1(c): Cumulative Total on Vaccination based on Age group

cumm_vac_age<- daily_vacc_hb %>% 
  filter(sex == "Total") %>% 
  filter(is.na(age_group_qf)) %>% 
  filter(date == max(date)) %>% 
  filter(hb_name =="Scotland") %>% 
  filter(age_group !="All vaccinations") %>% 
  mutate (cumulative_percent_coverage = ifelse(cumulative_percent_coverage >100, 100,
                                               round(cumulative_percent_coverage,2))) %>% 
  select(dose,age_group, cumulative_percent_coverage, population)
  
cumm_vac_age_plot <- cumm_vac_age %>% 
  ggplot()+
  aes(x = age_group, y = cumulative_percent_coverage)+
  geom_col(aes(fill = dose), position = "dodge")+
   # geom_text(aes(label=cumulative_percent_coverage, hjust = 0),
   #           position=position_dodge(width=0.9),angle = 90)+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Percentage Coverage of Vaccination by age group") +
  xlab("Age group") +
  ylab("% of Coverage") +
  color_theme()+
  scale_fill_manual(values = c("#f1a340", "#5ab4ac"))

ggplotly(cumm_vac_age_plot)

#position=position_dodge(width=0.9), vjust=-0.25

Forecast on Vaccination: ARIMA Model

Data Preparation

trend_vacc_hb <- trend_vacc_hb %>% 
  filter (dose == "Dose 2") %>% 
  select(date,cumulative_number_vaccinated)

# Convert it to zoo type
daily_vacc_hb_zoo <- zoo(trend_vacc_hb$cumulative_number_vaccinated, 
           order.by=as.Date(trend_vacc_hb$date, format='%m/%d/%Y'))

# Convert it into a time series
daily_vacc_hb_timeseries <-timeSeries::as.timeSeries(daily_vacc_hb_zoo)

Step 1 : Visualize the time series

original_series<-
  autoplot(daily_vacc_hb_timeseries, colour = '#5ab4ac')+
  xlab("Month") + 
  ylab("VACCINATED")+
  #scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Original Series") +
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  color_theme()
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
ggplotly(original_series)

Step 2 : Identification of model : (Finding d:)

Identify whether the time series is stationary / non stationary we can use ADF Augmented Dickey-Fuller test

adf_test <- adf.test(daily_vacc_hb_timeseries)

The time series is not stationary since we have a high p-value. So we apply difference

first_diff_ts<- diff(daily_vacc_hb_timeseries)
adf_test1 <- adf.test(na.omit(first_diff_ts))
second_diff_ts<- diff(first_diff_ts)
adf_test2 <- adf.test(na.omit(second_diff_ts))
Warning in adf.test(na.omit(second_diff_ts)) :
  p-value smaller than printed p-value
adf_test1

    Augmented Dickey-Fuller Test

data:  na.omit(first_diff_ts)
Dickey-Fuller = -0.63526, Lag order = 6, p-value = 0.9753
alternative hypothesis: stationary
adf_test2

    Augmented Dickey-Fuller Test

data:  na.omit(second_diff_ts)
Dickey-Fuller = -6.2431, Lag order = 6, p-value = 0.01
alternative hypothesis: stationary

Create a dataframe to compare

adf_data <- data.frame(Data = c("Original", "First-Ordered", "Second Ordered"),
                       Dickey_Fuller = c(adf_test$statistic, adf_test1$statistic, adf_test2$statistic),
                       p_value = c(adf_test$p.value,adf_test1$p.value,adf_test2$p.value))
adf_data

Initially the p-value is high which indicates that the Time Series is not stationary. So we apply difference 2 times. After the second difference, the p-value < significance level (0.05) So we can conclude that the difference data are stationary. So difference (d = 2)

Other method to confirm

ndiffs(daily_vacc_hb_timeseries)
[1] 2

Let’s plot the First Order and Second Order Difference Series

Order of first difference


first_order<- autoplot(first_diff_ts, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("VACCINATED")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("First-Order Difference") +
  color_theme()

ggplotly(first_order)

Order of Second difference


second_order<- autoplot(second_diff_ts, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("VACCINATED")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Second-Order Difference") +
  color_theme()

ggplotly(second_order)

Step 3 Estimate the parameters (Finding p and q)

For our model ARIMA (p,d,q), we found d = 2, the next step is to get the values of p and q, the order of AR and MA part. Plot ACF and PACF charts to identify q and p respectively.

 [1] 0.91 0.87 0.83 0.81 0.80 0.80 0.83 0.76 0.72 0.68 0.66 0.65 0.65 0.68 0.60 0.56 0.53 0.52 0.53 0.55 0.57 0.52 0.49
[24] 0.46 0.45 0.45 0.46
 [1] -0.30  0.00 -0.11 -0.08 -0.03 -0.19  0.61 -0.18 -0.05 -0.06 -0.09 -0.04 -0.13  0.53 -0.17 -0.06 -0.12 -0.07 -0.08
[20] -0.05  0.47 -0.17  0.01 -0.09 -0.05 -0.07 -0.09
 [1]  0.91  0.25  0.06  0.12  0.15  0.13  0.31 -0.47 -0.11  0.08  0.01  0.05  0.07  0.05 -0.25 -0.04  0.04  0.13  0.10
[20]  0.11  0.03 -0.13  0.03 -0.05 -0.06 -0.01 -0.01
 [1] -0.30 -0.10 -0.15 -0.18 -0.16 -0.36  0.48  0.12 -0.10 -0.02 -0.06 -0.08 -0.06  0.23  0.03 -0.05 -0.16 -0.12 -0.13
[20] -0.04  0.11 -0.05  0.06  0.07  0.02  0.01 -0.12

The ACF and PACF plots of the differenced data show the following patterns:

The ACF doesn’t follow a sinusoidal pattern but its slowly geometric decay. Also there is a significant spike at lag 3 in the PACF, but none beyond lag 3. So the data may follow an AR(3) model

The PACF is sinusoidal and decaying. Also there is a significant spike at lag 2 in the ACF, but none beyond lag 2 So the data may follow an MA(2) model

So we propose three ARMA models for the differenced data: ARMA(p,q) ARMA(3,2), ARMA(3,0) and ARMA(0,2).

That is, for the original time series, we propose three ARIMA models,ARIMA(p,d,q) ARIMA(3,1,2), ARIMA(3,1,0) and ARMA(3,1,2).

Step 4 Build the ARIMA model

Manual ARIMA:

arima_fit1 = Arima(daily_vacc_hb_timeseries, order = c(3,1,2))
arima_fit2 = Arima(daily_vacc_hb_timeseries, order = c(3,1,0))
arima_fit3 = Arima(daily_vacc_hb_timeseries, order = c(3,1,2))
arima_fit4 = Arima(daily_vacc_hb_timeseries, order = c(3,1,1))
summary(arima_fit1)
Series: daily_vacc_hb_timeseries 
ARIMA(3,1,2) 

Coefficients:
          ar1     ar2     ar3     ma1     ma2
      -0.8033  0.7639  0.9315  1.6734  0.8412
s.e.   0.0267  0.0254  0.0252  0.0465  0.0449

sigma^2 estimated as 18863412:  log likelihood=-2702.84
AIC=5417.68   AICc=5417.99   BIC=5439.4

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE      MASE        ACF1
Training set 433.8442 4295.907 2469.754 0.5542756 1.952307 0.1774188 -0.07419684
summary(arima_fit2)
Series: daily_vacc_hb_timeseries 
ARIMA(3,1,0) 

Coefficients:
         ar1     ar2     ar3
      0.6652  0.2214  0.0863
s.e.  0.0598  0.0707  0.0597

sigma^2 estimated as 20641665:  log likelihood=-2715.78
AIC=5439.56   AICc=5439.71   BIC=5454.04

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE     MASE        ACF1
Training set 387.7177 4510.387 2628.715 0.6106367 2.046611 0.188838 -0.01752897
summary(arima_fit3)
Series: daily_vacc_hb_timeseries 
ARIMA(3,1,2) 

Coefficients:
          ar1     ar2     ar3     ma1     ma2
      -0.8033  0.7639  0.9315  1.6734  0.8412
s.e.   0.0267  0.0254  0.0252  0.0465  0.0449

sigma^2 estimated as 18863412:  log likelihood=-2702.84
AIC=5417.68   AICc=5417.99   BIC=5439.4

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE      MASE        ACF1
Training set 433.8442 4295.907 2469.754 0.5542756 1.952307 0.1774188 -0.07419684
summary(arima_fit4)
Series: daily_vacc_hb_timeseries 
ARIMA(3,1,1) 

Coefficients:
         ar1      ar2      ar3      ma1
      1.3482  -0.3143  -0.0387  -0.7437
s.e.  0.1077   0.1081   0.0745   0.0882

sigma^2 estimated as 19748943:  log likelihood=-2709.19
AIC=5428.37   AICc=5428.6   BIC=5446.48

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE      MASE        ACF1
Training set 284.0437 4403.688 2569.029 0.6859451 1.971568 0.1845503 0.002057572

Forecast the Manual ARIMA model

# Forecast the manual models

future = forecast(arima_fit1, h = 30)
future2 = forecast(arima_fit2, h = 30)
future3 = forecast(arima_fit3, h = 30)
future4 = forecast(arima_fit4, h = 30)

#Plot the forecasted manual models

par(mfrow = c(2,2))
plot(future)
plot(future2)
plot(future3)
plot(future4)

Automated ARIMA

auto_arima_fit_vacc <- auto.arima(daily_vacc_hb_timeseries,
                  seasonal=FALSE,
                  stepwise = FALSE,
                  approximation = FALSE,
                  trace = TRUE
                  )

 ARIMA(0,2,0)                    : 5442.016
 ARIMA(0,2,1)                    : 5411.092
 ARIMA(0,2,2)                    : 5407.077
 ARIMA(0,2,3)                    : 5403.618
 ARIMA(0,2,4)                    : 5403.786
 ARIMA(0,2,5)                    : 5400.821
 ARIMA(1,2,0)                    : 5419.005
 ARIMA(1,2,1)                    : 5404.292
 ARIMA(1,2,2)                    : 5406.203
 ARIMA(1,2,3)                    : 5404.969
 ARIMA(1,2,4)                    : 5402.265
 ARIMA(2,2,0)                    : 5418.531
 ARIMA(2,2,1)                    : 5406.064
 ARIMA(2,2,2)                    : 5407.433
 ARIMA(2,2,3)                    : 5391.866
 ARIMA(3,2,0)                    : 5414.38
 ARIMA(3,2,1)                    : 5403.501
 ARIMA(3,2,2)                    : 5362.21
 ARIMA(4,2,0)                    : 5407.179
 ARIMA(4,2,1)                    : 5401.006
 ARIMA(5,2,0)                    : 5402.384



 Best model: ARIMA(3,2,2)                    
summary(auto_arima_fit_vacc)
Series: daily_vacc_hb_timeseries 
ARIMA(3,2,2) 

Coefficients:
         ar1      ar2      ar3      ma1     ma2
      0.8243  -0.4487  -0.4145  -1.2768  0.9217
s.e.  0.0606   0.0713   0.0574   0.0361  0.0315

sigma^2 estimated as 16635995:  log likelihood=-2674.95
AIC=5361.9   AICc=5362.21   BIC=5383.6

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE      MASE       ACF1
Training set 16.38254 4026.859 2315.206 0.4405824 1.835199 0.1663165 -0.0592707

Model Selection Criteria :

ARIMA models with minimum AIC, RMSE and MAPE criteria were chosen as the best models. Automated ARIMA confirms that the ARIMA(3, 2, 2) seems good based on AIC

lmtest::coeftest(auto_arima_fit_vacc)

z test of coefficients:

     Estimate Std. Error  z value  Pr(>|z|)    
ar1  0.824250   0.060551  13.6124 < 2.2e-16 ***
ar2 -0.448709   0.071338  -6.2899 3.176e-10 ***
ar3 -0.414539   0.057388  -7.2234 5.069e-13 ***
ma1 -1.276757   0.036124 -35.3440 < 2.2e-16 ***
ma2  0.921711   0.031529  29.2335 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

All the coefficients are statistically significant.

Step 5 Check for Diagnostics

Let’s plot the diagnostics with the results to make sure the normality and correlation assumptions for the model hold. If the residuals look like white noise, proceed with forecast and prediction, otherwise repeat the model building.

res <- checkresiduals(auto_arima_fit_vacc, theme = color_theme())

    Ljung-Box test

data:  Residuals from ARIMA(3,2,2)
Q* = 103.21, df = 5, p-value < 2.2e-16

Model df: 5.   Total lags used: 10

res

    Ljung-Box test

data:  Residuals from ARIMA(3,2,2)
Q* = 103.21, df = 5, p-value < 2.2e-16

The ACF plot of the residuals from the ARIMA(3,2,2) model shows that almost auto correlationswith regular interval outlier. A portmanteau test returns a smaller p-value (almost close to Zero), also suggesting that the residuals are white noise.

Fitting the ARIMA model with the existing data

The residual errors seem fine with near zero mean and uniform variance. Let’s plot the actuals against the fitted values

#Convert the model to dataframe for plotting

daily_vacc_hb_timeseries_data <- fortify(daily_vacc_hb_timeseries) %>% 
  clean_names() %>% 
  remove_rownames %>% 
  rename (date = index,
          vacc = data)%>% 
  mutate(index = seq(1:nrow(daily_vacc_hb_timeseries)))
  
arima_fit_resid <- ts(daily_vacc_hb_timeseries) - resid(auto_arima_fit_vacc)

arima_fit_data <- fortify(arima_fit_resid) %>% 
  clean_names() %>% 
  mutate(data = round(data,2))

fit_existing_data <- daily_vacc_hb_timeseries_data %>% 
  inner_join(arima_fit_data, by = c("index"))
#plotting the series along with the fitted values
fit_existing_data %>% 
  ggplot()+
  aes(x=date, y = vacc)+
  geom_line(color ="#5ab4ac")+
  geom_line(aes(x= date, y = data), colour = "red" )+
  xlab("Month") + 
  ylab("Number of People vaccinated")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Fitting the ARIMA model with existing data") +
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  color_theme()

Step 6 Forecast using the model

Data Preparation :

#Convert the model to dataframe for plotting
forecast_model <- forecast(auto_arima_fit_vacc,level = c(80, 95), h = 60) 

forecast_model_data <- fortify(forecast_model) %>% 
  clean_names() %>% 
  mutate(data = round(data,2),
         fitted= round(fitted,2)) 

forecast_start_date <- as.Date(max(daily_vacc_hb_timeseries_data$date)+1)
forecast_end_date <- as.Date(forecast_start_date+59)

forecast_data <- forecast_model_data %>% 
  filter(!(is.na(point_forecast))) %>% 
  mutate(date = seq(forecast_start_date,forecast_end_date, by =1)) %>% 
select(-data,-fitted, -index)  

fitted_data <- forecast_model_data %>% 
  filter(!(is.na(data))) %>% 
  inner_join(daily_vacc_hb_timeseries_data, by = c("index")) %>% 
  mutate(date = as.Date(date)) %>% 
select(date, data, fitted) 

Plotting the Vaccination series plus the forecast and 95% prediction intervals

annotation <- data.frame(
   x = c(as.Date("03-04-2021","%d-%m-%Y"),as.Date("31-10-2021","%d-%m-%Y")),
   y = c(1000000,3000000),
   label = c("PAST", "FUTURE")
)

#Time series plots for the next 60 days according to best ARIMA models with 80%–95% CI.
fitted_data %>% 
  ggplot()+
  geom_line(aes(x= date, y = data))+
  geom_line(aes(x= date, y = fitted), colour = "red" )+
  geom_line(aes(x= date, y =point_forecast), data = forecast_data )+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_80, ymax = hi_80), 
              data = forecast_data, alpha = 0.3, fill = "green")+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_95, ymax = hi_95), 
              data = forecast_data, alpha = 0.1)+
  ggtitle("Forecast") +
  xlab("Month") + 
  ylab("Number of People vaccinated")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  color_theme()+
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
   geom_text(data=annotation, 
             aes( x=x, y=y, label=label),                  
            color="red", 
            size=4 )+
  geom_vline(xintercept =as.Date("08-10-2021","%d-%m-%Y"), linetype = 2)

“Forecast on Hospitalisation (ARIMA Modelling)”

Data Preparation

#For forecasting, we chose the latest data
trend_hosp_hb <- trend_hb_daily %>% 
  filter (hb_name == "Scotland") %>% 
  filter(date >="2021-06-01") %>% 
  filter(!(is.na(hospital_admissions))) %>% 
  select(date, hospital_admissions)

# Convert it into a time series
daily_hosp_hb_zoo <- zoo(trend_hosp_hb$hospital_admissions, 
           order.by=as.Date(trend_hosp_hb$date, format='%m/%d/%Y'))

# Convert it into a time series
daily_hosp_hb_timeseries <-  timeSeries::as.timeSeries(daily_hosp_hb_zoo)

Step 1 : Visualize the time series

original_series<-autoplot(daily_hosp_hb_timeseries, ts.colour = '#5ab4ac')+
  xlab("Month") + 
  ylab("Number of People hospitalised")+
  #scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Trend on Hospitalisation") +
  color_theme()

ggplotly(original_series)

Step 2 : Identification of model : (Finding d:)

Identify whether the time series is stationary / non stationary we can use ADF Augmented Dickey-Fuller test

adf_test_hosp <- adf.test(daily_hosp_hb_timeseries)
adf_test_hosp

    Augmented Dickey-Fuller Test

data:  daily_hosp_hb_timeseries
Dickey-Fuller = -1.0883, Lag order = 4, p-value = 0.9209
alternative hypothesis: stationary

The time series is not stationary since we have a high p-value (p-value must be < 0.05). So we apply difference

first_diff_hosp<- diff(daily_hosp_hb_timeseries)
adf_test1_hosp <- adf.test(na.omit(first_diff_hosp))
Warning in adf.test(na.omit(first_diff_hosp)) :
  p-value smaller than printed p-value
adf_test1_hosp

    Augmented Dickey-Fuller Test

data:  na.omit(first_diff_hosp)
Dickey-Fuller = -7.5072, Lag order = 4, p-value = 0.01
alternative hypothesis: stationary

Create a dataframe to compare

adf_data_hosp <- data.frame(Data = c("Original", "First-Ordered"),
                       Dickey_Fuller = c(adf_test_hosp$statistic, adf_test1_hosp$statistic),
                       p_value = c(adf_test_hosp$p.value,adf_test1_hosp$p.value))
adf_data_hosp

Initially the p-value is high which indicates that the Time Series is not stationary. So we apply difference 1 time. After the first difference, the p-value < significance level (0.05) So we can conclude that the difference data are stationary. So difference (d = 1)

Other method:

ndiffs(daily_hosp_hb_timeseries)
[1] 1

Let’s plot the First Order Difference Series

Order of first difference

p<- autoplot(first_diff_hosp, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("HOSPITALIZATION")+
 # scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("First-Order Difference Series") +
  color_theme()

ggplotly(p)

Step 3: Estimate the parameters (Finding p and q)

For our model ARIMA (p,d,q), we found d = 1, the next step is to get the values of p and q, the order of AR and MA part. Plot ACF and PACF charts to identify q and p respectively.

par(mfrow=c(2,2))
acf_hosp  <- acf1(daily_hosp_hb_timeseries, col=2:7, lwd=4)
pacf_hosp <- acf1(daily_hosp_hb_timeseries,  pacf = TRUE, col=2:7, lwd=4)
acf_hosp  <- acf1(first_diff_hosp, col=2:7, lwd=4)
pacf_hosp <- acf1(first_diff_hosp,  pacf = TRUE, col=2:7, lwd=4)

The ACF and PACF plots of the differenced data show the following patterns:

The ACF is sinusoidal and there is a significant spike at lag 3 in the PACF So the data may follow an AR(3) model

The PACF is sinusoidal and there is a significant spike at lag 2 in the ACF So the data may follow an MA(2) model

So we propose three ARMA models for the differenced data: ARMA(p,q) ARMA(3,2), ARMA(3,0) and ARMA(0,2).

That is, for the original time series, we propose three ARIMA models,ARIMA(p,d,q) ARIMA(3,1,2), ARIMA(3,1,0) and ARMA(0,1,2).

Step 4: Build the ARIMA model

Manual ARIMA:

arima_fit_hosp_1 = Arima(daily_hosp_hb_timeseries, order = c(3,1,2))
arima_fit_hosp_2 = Arima(daily_hosp_hb_timeseries, order = c(3,1,0))
arima_fit_hosp_3 = Arima(daily_hosp_hb_timeseries, order = c(0,1,2))
summary(arima_fit_hosp_1)
Series: daily_hosp_hb_timeseries 
ARIMA(3,1,2) 

Coefficients:
         ar1      ar2      ar3      ma1     ma2
      0.9213  -0.5824  -0.3100  -1.3525  1.0000
s.e.  0.0896   0.1115   0.0903   0.0328  0.0408

sigma^2 estimated as 208.2:  log likelihood=-495.31
AIC=1002.61   AICc=1003.35   BIC=1019.39

Training set error measures:
                    ME     RMSE     MAE       MPE     MAPE      MASE        ACF1
Training set 0.2478801 14.06887 11.2004 -2.586156 17.88154 0.8402037 -0.05606368
summary(arima_fit_hosp_2)
Series: daily_hosp_hb_timeseries 
ARIMA(3,1,0) 

Coefficients:
          ar1      ar2      ar3
      -0.2589  -0.2312  -0.1612
s.e.   0.0934   0.0941   0.0931

sigma^2 estimated as 273.9:  log likelihood=-509.84
AIC=1027.69   AICc=1028.03   BIC=1038.87

Training set error measures:
                    ME     RMSE     MAE       MPE     MAPE      MASE        ACF1
Training set 0.4623877 16.27678 12.7506 -2.946581 19.70259 0.9564925 -0.02137951
summary(arima_fit_hosp_3)
Series: daily_hosp_hb_timeseries 
ARIMA(0,1,2) 

Coefficients:
          ma1      ma2
      -0.2801  -0.1740
s.e.   0.0898   0.0775

sigma^2 estimated as 270.5:  log likelihood=-509.6
AIC=1025.2   AICc=1025.4   BIC=1033.58

Training set error measures:
                    ME     RMSE      MAE       MPE     MAPE      MASE         ACF1
Training set 0.5474528 16.24428 12.69526 -2.910392 19.77577 0.9523412 0.0004090666

texreg::screenreg(list(arima_fit_hosp_1, arima_fit_hosp_2, arima_fit_hosp_3),
                custom.model.names =c("ARIMA(3,1,2)","ARIMA(3,1,0)","ARIMA(0,1,4)"),
                center = TRUE,
                table = FALSE)

========================================================
                ARIMA(3,1,2)  ARIMA(3,1,0)  ARIMA(0,1,4)
--------------------------------------------------------
ar1                0.92 ***     -0.26 **                
                  (0.09)        (0.09)                  
ar2               -0.58 ***     -0.23 *                 
                  (0.11)        (0.09)                  
ar3               -0.31 ***     -0.16                   
                  (0.09)        (0.09)                  
ma1               -1.35 ***                   -0.28 **  
                  (0.03)                      (0.09)    
ma2                1.00 ***                   -0.17 *   
                  (0.04)                      (0.08)    
--------------------------------------------------------
AIC             1002.61       1027.69       1025.20     
AICc            1003.35       1028.03       1025.40     
BIC             1019.39       1038.87       1033.58     
Log Likelihood  -495.31       -509.84       -509.60     
Num. obs.        121           121           121        
========================================================
*** p < 0.001; ** p < 0.01; * p < 0.05
#Function for Automated ARIMA


auto_arima_fit_hosp <- auto.arima(lag(daily_hosp_hb_timeseries,k =lag_value ),
                  seasonal=FALSE,
                  stepwise=FALSE,
                  approximation=FALSE,
                  trace = TRUE
                  )

 ARIMA(0,1,0)                    : 1017.948
 ARIMA(0,1,0) with drift         : 1019.919
 ARIMA(0,1,1)                    : 1010.762
 ARIMA(0,1,1) with drift         : 1012.508
 ARIMA(0,1,2)                    : 1008.339
 ARIMA(0,1,2) with drift         : 1009.948
 ARIMA(0,1,3)                    : 1010.476
 ARIMA(0,1,3) with drift         : 1012.124
 ARIMA(0,1,4)                    : 997.2108
 ARIMA(0,1,4) with drift         : 999.0964
 ARIMA(0,1,5)                    : 992.7933
 ARIMA(0,1,5) with drift         : 994.8451
 ARIMA(1,1,0)                    : 1014.76
 ARIMA(1,1,0) with drift         : 1016.692
 ARIMA(1,1,1)                    : 1009.347
 ARIMA(1,1,1) with drift         : 1010.975
 ARIMA(1,1,2)                    : 1010.478
 ARIMA(1,1,2) with drift         : 1012.125
 ARIMA(1,1,3)                    : 1011.966
 ARIMA(1,1,3) with drift         : 1013.657
 ARIMA(1,1,4)                    : Inf
 ARIMA(1,1,4) with drift         : Inf
 ARIMA(2,1,0)                    : 1011.524
 ARIMA(2,1,0) with drift         : 1013.37
 ARIMA(2,1,1)                    : 1010.109
 ARIMA(2,1,1) with drift         : 1011.745
 ARIMA(2,1,2)                    : Inf
 ARIMA(2,1,2) with drift         : Inf
 ARIMA(2,1,3)                    : Inf
 ARIMA(2,1,3) with drift         : Inf
 ARIMA(3,1,0)                    : 1012.059
 ARIMA(3,1,0) with drift         : 1013.866
 ARIMA(3,1,1)                    : 1011.959
 ARIMA(3,1,1) with drift         : 1013.614
 ARIMA(3,1,2)                    : 984.7138
 ARIMA(3,1,2) with drift         : 986.4505
 ARIMA(4,1,0)                    : 1012.409
 ARIMA(4,1,0) with drift         : 1014.144
 ARIMA(4,1,1)                    : 1012.594
 ARIMA(4,1,1) with drift         : 1014.198
 ARIMA(5,1,0)                    : 1006.976
 ARIMA(5,1,0) with drift         : 1008.398



 Best model: ARIMA(3,1,2)                    

Automated ARIMA

#Lag is used to best fit the model
auto_arima_fit_hosp <- auto.arima(lag(daily_hosp_hb_timeseries),
                  seasonal=FALSE,
                  stepwise=FALSE,
                  approximation=FALSE,
                  trace = TRUE
                  )

 ARIMA(0,1,0)                    : 1017.948
 ARIMA(0,1,0) with drift         : 1019.919
 ARIMA(0,1,1)                    : 1010.762
 ARIMA(0,1,1) with drift         : 1012.508
 ARIMA(0,1,2)                    : 1008.339
 ARIMA(0,1,2) with drift         : 1009.948
 ARIMA(0,1,3)                    : 1010.476
 ARIMA(0,1,3) with drift         : 1012.124
 ARIMA(0,1,4)                    : 997.2108
 ARIMA(0,1,4) with drift         : 999.0964
 ARIMA(0,1,5)                    : 992.7933
 ARIMA(0,1,5) with drift         : 994.8451
 ARIMA(1,1,0)                    : 1014.76
 ARIMA(1,1,0) with drift         : 1016.692
 ARIMA(1,1,1)                    : 1009.347
 ARIMA(1,1,1) with drift         : 1010.975
 ARIMA(1,1,2)                    : 1010.478
 ARIMA(1,1,2) with drift         : 1012.125
 ARIMA(1,1,3)                    : 1011.966
 ARIMA(1,1,3) with drift         : 1013.657
 ARIMA(1,1,4)                    : Inf
 ARIMA(1,1,4) with drift         : Inf
 ARIMA(2,1,0)                    : 1011.524
 ARIMA(2,1,0) with drift         : 1013.37
 ARIMA(2,1,1)                    : 1010.109
 ARIMA(2,1,1) with drift         : 1011.745
 ARIMA(2,1,2)                    : Inf
 ARIMA(2,1,2) with drift         : Inf
 ARIMA(2,1,3)                    : Inf
 ARIMA(2,1,3) with drift         : Inf
 ARIMA(3,1,0)                    : 1012.059
 ARIMA(3,1,0) with drift         : 1013.866
 ARIMA(3,1,1)                    : 1011.959
 ARIMA(3,1,1) with drift         : 1013.614
 ARIMA(3,1,2)                    : 984.7138
 ARIMA(3,1,2) with drift         : 986.4505
 ARIMA(4,1,0)                    : 1012.409
 ARIMA(4,1,0) with drift         : 1014.144
 ARIMA(4,1,1)                    : 1012.594
 ARIMA(4,1,1) with drift         : 1014.198
 ARIMA(5,1,0)                    : 1006.976
 ARIMA(5,1,0) with drift         : 1008.398



 Best model: ARIMA(3,1,2)                    
auto_arima_fit_hosp
Series: lag(daily_hosp_hb_timeseries) 
ARIMA(3,1,2) 

Coefficients:
        ar1      ar2      ar3      ma1     ma2
      0.942  -0.5638  -0.2542  -1.4378  0.9367
s.e.  0.098   0.1137   0.1001   0.0545  0.0409

sigma^2 estimated as 196.2:  log likelihood=-485.99
AIC=983.97   AICc=984.71   BIC=1000.7

Automated ARIMA confirms that the ARIMA(3,1,2) seems good based on AIC

coef<-lmtest::coeftest(auto_arima_fit_hosp)
coef

z test of coefficients:

     Estimate Std. Error  z value  Pr(>|z|)    
ar1  0.942010   0.098031   9.6093 < 2.2e-16 ***
ar2 -0.563822   0.113727  -4.9577 7.135e-07 ***
ar3 -0.254174   0.100149  -2.5380   0.01115 *  
ma1 -1.437823   0.054505 -26.3795 < 2.2e-16 ***
ma2  0.936722   0.040946  22.8771 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

All coefficients are significant except ar3.

Model Selection Criteria :

ARIMA models with minimum AIC, RMSE and MAPE criteria were chosen as the best models. Based on Akaike Information Criterion (AIC) above, an ARIMA(3, 1, 2) model seems best.

Step 5: Check for Diagnostics

Let’s plot the diagnostics with the results to make sure the normality and correlation assumptions for the model hold. If the residuals look like white noise, proceed with forecast and prediction, otherwise repeat the model building.

res <-checkresiduals(auto_arima_fit_hosp, theme = color_theme())

    Ljung-Box test

data:  Residuals from ARIMA(3,1,2)
Q* = 33.437, df = 5, p-value = 3.082e-06

Model df: 5.   Total lags used: 10

res

    Ljung-Box test

data:  Residuals from ARIMA(3,1,2)
Q* = 33.437, df = 5, p-value = 3.082e-06

The ACF plot of the residuals from the ARIMA(3,1,2) model shows that all auto correlations are almost within the threshold limits, with residuals. A portmanteau test (Ljung-Box test) returns a smaller p-value , also suggesting that the residuals are white noise.

Fitting the ARIMA model with the existing data

The residual errors seem fine with near zero mean and uniform variance. Let’s plot the actuals against the fitted values

Convert model and time series to dataframe for plotting

daily_hosp_hb_timeseries_data <- fortify(daily_hosp_hb_timeseries) %>% 
  clean_names() %>% 
  remove_rownames %>% 
  rename (date = index,
          hosp = data)%>% 
  mutate(index = seq(1:nrow(daily_hosp_hb_timeseries)))
  
arima_fit_resid <- ts(daily_hosp_hb_timeseries[1:nrow(daily_hosp_hb_timeseries)]) - resid(auto_arima_fit_hosp)

arima_fit_data <- fortify(arima_fit_resid) %>% 
  clean_names() %>% 
  mutate(data = round(data,2))

fit_existing_data <- daily_hosp_hb_timeseries_data %>% 
  inner_join(arima_fit_data, by = c("index"))

Plotting the series along with the fitted values

fit_existing_hosp_plot <- fit_existing_data %>% 
  mutate (date = as.Date(date)) %>% 
  ggplot()+
  aes(x=date, y = hosp)+
  geom_line(color ="#5ab4ac")+
  geom_line(aes(x= date, y = data), colour = "red" )+
  xlab("Month") + 
  ylab("Patient Hospitalised")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Fitting the ARIMA model with existing data") +
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  #scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  color_theme()

ggplotly(fit_existing_hosp_plot)

Step 6 Forecast using the model

Data Preparation :

forecast_model <- forecast(auto_arima_fit_hosp,level = c(80, 95), h = 30) 

#Convert the model to dataframe for plotting

forecast_model_data <- fortify(forecast_model) %>% 
  clean_names() %>% 
  mutate(data = round(data,2),
         fitted= round(fitted,2)) %>% 
  mutate (lo_80 = ifelse(lo_80 < 0,0,lo_80),
          lo_95 = ifelse(lo_95 < 0,0,lo_95)
  )

forecast_start_date <- as.Date(max(daily_hosp_hb_timeseries_data$date)+1)
forecast_end_date <- as.Date(forecast_start_date+29)

forecast_data <- forecast_model_data %>% 
  filter(!(is.na(point_forecast))) %>% 
  mutate(date = seq(forecast_start_date,forecast_end_date, by =1)) %>% 
select(-data,-fitted, -index)  

fitted_data <- forecast_model_data %>% 
  filter(!(is.na(data))) %>% 
  inner_join(daily_hosp_hb_timeseries_data, by = c("index")) %>% 
  mutate(date = as.Date(date)) %>% 
select(date, data, fitted) 

Plotting the Vaccination series plus the forecast and 80 - 95% prediction intervals


annotation <- data.frame(
   x = c(as.Date("13-08-2021","%d-%m-%Y"),as.Date("31-10-2021","%d-%m-%Y")),
   y = c(180,200),
   label = c("PAST", "FUTURE")
)

#Time series plots for the next 60 days according to best ARIMA models with 80%–95% CI.
forecast_data_hosp_plot <-fitted_data %>% 
  ggplot()+
  geom_line(aes(x= date, y = data), color = "#5ab4ac")+
  #geom_line(aes(x= date, y = fitted), colour = "red" )+
  geom_line(aes(x= date, y =point_forecast), color ="blue", size = 0.5,
             data = forecast_data )+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_80, ymax = hi_80), 
              data = forecast_data, alpha = 0.3, fill = "green")+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_95, ymax = hi_95), 
              data = forecast_data, alpha = 0.1)+
  geom_vline(aes(xintercept=as.numeric(max(date))),color="#f1a340", linetype="dashed",data = fitted_data)+
  ggtitle("Projection of Hospitalisation") +
  xlab("Month") + 
  ylab("Patient Hospitalised")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  color_theme()+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
   geom_text(data=annotation, 
             aes( x=x, y=y, label=label),                  
            color="blue", 
            size=4 )
  
  ggplotly(forecast_data_hosp_plot)
NA

“Forecast on Deaths (ARIMA Modelling)”

Data Preparation

#For forecasting, we chose the latest data
trend_death_hb <- trend_hb_daily %>% 
  filter (hb_name == "Scotland") %>% 
  filter(date >="2021-06-01") %>% 
  filter(!(is.na(daily_deaths))) %>% 
  select(date, daily_deaths)

# Convert it into a time series
daily_death_hb_zoo <- zoo(trend_death_hb$daily_deaths, 
           order.by=as.Date(trend_death_hb$date, format='%m/%d/%Y'))

# Convert it into a time series
daily_death_hb_timeseries <-  timeSeries::as.timeSeries(daily_death_hb_zoo)

Step 1 : Visualize the time series

original_series_death<-autoplot(daily_death_hb_timeseries, ts.colour = '#5ab4ac')+
  xlab("Month") + 
  ylab("Patient died")+
  #scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Trend on Deaths") +
  color_theme()

ggplotly(original_series_death)

Step 2 : Identification of model : (Finding d:)

Identify whether the time series is stationary / non stationary we can use ADF Augmented Dickey-Fuller test

adf_test_death <- adf.test(daily_death_hb_timeseries)
adf_test_death

    Augmented Dickey-Fuller Test

data:  daily_death_hb_timeseries
Dickey-Fuller = -1.4701, Lag order = 4, p-value = 0.7967
alternative hypothesis: stationary

The time series is not stationary since we have a high p-value (p-value must be < 0.05). So we apply difference

first_diff_death<- diff(daily_death_hb_timeseries)
adf_test1_death <- adf.test(na.omit(first_diff_death))
Warning in adf.test(na.omit(first_diff_death)) :
  p-value smaller than printed p-value
adf_test1_death

    Augmented Dickey-Fuller Test

data:  na.omit(first_diff_death)
Dickey-Fuller = -5.5546, Lag order = 4, p-value = 0.01
alternative hypothesis: stationary

Create a dataframe to compare

adf_data_death <- data.frame(Data = c("Original", "First-Ordered"),
                       Dickey_Fuller = c(adf_test_death$statistic, adf_test1_death$statistic),
                       p_value = c(adf_test_death$p.value,adf_test1_death$p.value))
adf_data_death

Initially the p-value is high which indicates that the Time Series is not stationary. So we apply difference 1 time. After the first difference, the p-value < significance level (0.05) So we can conclude that the difference data are stationary. So difference (d = 1)

Other method:

ndiffs(daily_death_hb_timeseries)
[1] 1

Let’s plot the First Order Difference Series

Order of first difference


first_diff_death<- diff(daily_death_hb_timeseries)
p<- autoplot(first_diff_death, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("DEATH")+
 # scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("First-Order Difference Series") +
  color_theme()

ggplotly(p)

Step 3 Estimate the parameters (Finding p and q)

For our model ARIMA (p,d,q), we found d = 1, the next step is to get the values of p and q, the order of AR and MA part. Plot ACF and PACF charts to identify q and p respectively.

par(mfrow=c(2,2))
acf_death  <- acf1(first_diff_death, col=2:7, lwd=4)
pacf_death <- acf1(first_diff_death,  pacf = TRUE, col=2:7, lwd=4)

The ACF and PACF plots tapers to zeo at one point. So it follows an ARMA model

The PACF non-zero value at lag 3 So the data may follow an AR(3) model

The ACF non - zero value at lag 2 So it can be MA (2)

So we propose three ARMA models for the differenced data: ARMA(p,q) ARMA(3,2), ARMA(3,0) and ARMA(0,2).

That is, for the original time series, we propose three ARIMA models,ARIMA(p,d,q) ARIMA(3,1,2), ARIMA(3,1,0) and ARMA(0,1,2).

Step 4 Build the ARIMA model

Manual ARIMA

arima_fit_dth_1 = Arima(daily_death_hb_timeseries, order = c(3,1,2))
arima_fit_dth_2 = Arima(daily_death_hb_timeseries, order = c(3,1,0))
arima_fit_dth_3 = Arima(daily_death_hb_timeseries, order = c(0,1,2))
summary(arima_fit_dth_1)
Series: daily_death_hb_timeseries 
ARIMA(3,1,2) 

Coefficients:
         ar1     ar2      ar3      ma1     ma2
      0.9171  0.2154  -0.3663  -1.7837  0.9143
s.e.  0.1133  0.1232   0.1185   0.0652  0.0721

sigma^2 estimated as 8.088:  log likelihood=-296.46
AIC=604.93   AICc=605.67   BIC=621.7

Training set error measures:
                    ME     RMSE      MAE  MPE MAPE      MASE       ACF1
Training set 0.2407185 2.773069 2.025545 -Inf  Inf 0.7208557 -0.0291094
summary(arima_fit_dth_2)
Series: daily_death_hb_timeseries 
ARIMA(3,1,0) 

Coefficients:
          ar1      ar2     ar3
      -0.8014  -0.2776  -0.091
s.e.   0.0913   0.1137   0.093

sigma^2 estimated as 8.593:  log likelihood=-300.63
AIC=609.27   AICc=609.61   BIC=620.45

Training set error measures:
                    ME     RMSE      MAE  MPE MAPE      MASE        ACF1
Training set 0.3192471 2.882911 2.161774 -Inf  Inf 0.7693371 -0.04370216
summary(arima_fit_dth_3)
Series: daily_death_hb_timeseries 
ARIMA(0,1,2) 

Coefficients:
          ma1     ma2
      -0.8169  0.2285
s.e.   0.0882  0.0773

sigma^2 estimated as 8.548:  log likelihood=-300.84
AIC=607.69   AICc=607.89   BIC=616.08

Training set error measures:
                    ME     RMSE      MAE  MPE MAPE      MASE        ACF1
Training set 0.3544154 2.887548 2.145374 -Inf  Inf 0.7635008 -0.04100283

Another way of checking AIC

texreg::screenreg(list(arima_fit_dth_1, arima_fit_dth_2, arima_fit_dth_3),
                custom.model.names =c("ARIMA(3,1,2)","ARIMA(3,1,0)","ARIMA(0,1,2)"),
                center = TRUE,
                table = FALSE)

========================================================
                ARIMA(3,1,2)  ARIMA(3,1,0)  ARIMA(0,1,2)
--------------------------------------------------------
ar1                0.92 ***     -0.80 ***               
                  (0.11)        (0.09)                  
ar2                0.22         -0.28 *                 
                  (0.12)        (0.11)                  
ar3               -0.37 **      -0.09                   
                  (0.12)        (0.09)                  
ma1               -1.78 ***                   -0.82 *** 
                  (0.07)                      (0.09)    
ma2                0.91 ***                    0.23 **  
                  (0.07)                      (0.08)    
--------------------------------------------------------
AIC              604.93        609.27        607.69     
AICc             605.67        609.61        607.89     
BIC              621.70        620.45        616.08     
Log Likelihood  -296.46       -300.63       -300.84     
Num. obs.        121           121           121        
========================================================
*** p < 0.001; ** p < 0.01; * p < 0.05

Based on this, ARIMA model (3,1,2) seems best. We can verify the same using automated method

Automated ARIMA

auto_arima_fit_death <- auto.arima(lag(daily_death_hb_timeseries),
                  seasonal=FALSE,
                  stepwise=FALSE,
                  approximation=FALSE,
                  trace = TRUE
                  )

 ARIMA(0,1,0)                    : 661.2495
 ARIMA(0,1,0) with drift         : 662.9805
 ARIMA(0,1,1)                    : 604.9241
 ARIMA(0,1,1) with drift         : 603.4592
 ARIMA(0,1,2)                    : 600.5927
 ARIMA(0,1,2) with drift         : 600.1606
 ARIMA(0,1,3)                    : 602.1154
 ARIMA(0,1,3) with drift         : 601.3862
 ARIMA(0,1,4)                    : 597.6822
 ARIMA(0,1,4) with drift         : 597.7631
 ARIMA(0,1,5)                    : 599.8704
 ARIMA(0,1,5) with drift         : 599.9091
 ARIMA(1,1,0)                    : 605.4993
 ARIMA(1,1,0) with drift         : 606.5658
 ARIMA(1,1,1)                    : 599.2604
 ARIMA(1,1,1) with drift         : 598.7763
 ARIMA(1,1,2)                    : 601.2259
 ARIMA(1,1,2) with drift         : 600.6498
 ARIMA(1,1,3)                    : 603.1702
 ARIMA(1,1,3) with drift         : 602.3916
 ARIMA(1,1,4)                    : 599.8811
 ARIMA(1,1,4) with drift         : 599.963
 ARIMA(2,1,0)                    : 601.7416
 ARIMA(2,1,0) with drift         : 602.2924
 ARIMA(2,1,1)                    : 601.1992
 ARIMA(2,1,1) with drift         : 600.5597
 ARIMA(2,1,2)                    : 603.3692
 ARIMA(2,1,2) with drift         : 602.7661
 ARIMA(2,1,3)                    : 592.0755
 ARIMA(2,1,3) with drift         : Inf
 ARIMA(3,1,0)                    : 603.1214
 ARIMA(3,1,0) with drift         : 603.4379
 ARIMA(3,1,1)                    : 603.3196
 ARIMA(3,1,1) with drift         : 602.7205
 ARIMA(3,1,2)                    : 599.3322
 ARIMA(3,1,2) with drift         : 600.2376
 ARIMA(4,1,0)                    : 596.0037
 ARIMA(4,1,0) with drift         : 594.9402
 ARIMA(4,1,1)                    : 594.7462
 ARIMA(4,1,1) with drift         : 594.1175
 ARIMA(5,1,0)                    : 593.284
 ARIMA(5,1,0) with drift         : 593.3211



 Best model: ARIMA(2,1,3)                    
auto_arima_fit_death
Series: lag(daily_death_hb_timeseries) 
ARIMA(2,1,3) 

Coefficients:
          ar1      ar2     ma1     ma2      ma3
      -1.6080  -0.9445  0.8866  0.0474  -0.4563
s.e.   0.0583   0.0518  0.0987  0.1133   0.0984

sigma^2 estimated as 7.525:  log likelihood=-289.67
AIC=591.33   AICc=592.08   BIC=608.06

However Automated ARIMA also confirms that the ARIMA(2,1,3) seems good based on AIC

coef_dth<-lmtest::coeftest(auto_arima_fit_death)
coef_dth

z test of coefficients:

     Estimate Std. Error  z value  Pr(>|z|)    
ar1 -1.608022   0.058276 -27.5932 < 2.2e-16 ***
ar2 -0.944495   0.051822 -18.2257 < 2.2e-16 ***
ma1  0.886589   0.098691   8.9835 < 2.2e-16 ***
ma2  0.047364   0.113311   0.4180    0.6759    
ma3 -0.456307   0.098377  -4.6384 3.512e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Model Selection Criteria :

ARIMA models with minimum AIC, RMSE and MAPE criteria were chosen as the best models. Based on Akaike Information Criterion (AIC) above, an ARIMA(2, 1, 3) model seems best.

Step 5 Check for Diagnostics

Let’s plot the diagnostics with the results to make sure the normality and correlation assumptions for the model hold. If the residuals look like white noise, proceed with forecast and prediction, otherwise repeat the model building.

res_dth <-checkresiduals(auto_arima_fit_death, theme = color_theme())

    Ljung-Box test

data:  Residuals from ARIMA(2,1,3)
Q* = 6.9535, df = 5, p-value = 0.2241

Model df: 5.   Total lags used: 10

res_dth

    Ljung-Box test

data:  Residuals from ARIMA(2,1,3)
Q* = 6.9535, df = 5, p-value = 0.2241

The ACF plot of the residuals from the ARIMA(2,1,3) model shows that all auto correlations are within the threshold limits except 1, But portmanteau test (Ljung-Box test) shows a higher p-value which means the values are indpendent. i.e We fail to reject the null hypothesis

Fitting the ARIMA model with the existing data

Let’s plot the actuals against the fitted values

Convert model and time series to dataframe for plotting

daily_death_hb_timeseries_data <- fortify(daily_death_hb_timeseries) %>% 
  clean_names() %>% 
  remove_rownames %>% 
  rename (date = index,
          death = data)%>% 
  mutate(index = seq(1:nrow(daily_death_hb_timeseries)))
  
arima_fit_dth_resid <- ts(daily_death_hb_timeseries[1:nrow(daily_death_hb_timeseries)]) - resid(auto_arima_fit_death)

arima_fit_dth_data <- fortify(arima_fit_dth_resid) %>% 
  clean_names() %>% 
  mutate(data = round(data,2))%>% 
  mutate (data = ifelse(data < 0,0,data))

fit_existing_dth_data <- daily_death_hb_timeseries_data %>% 
  inner_join(arima_fit_dth_data, by = c("index"))

Plotting the series along with the fitted values


fit_existing_dth_plot <- fit_existing_dth_data %>% 
   mutate (data = ifelse(data < 0,0,data)) %>% 
  mutate(date = as.Date(date)) %>% 
  ggplot()+
  aes(x=date, y = death)+
  geom_line(color ="#5ab4ac")+
  geom_line(aes(x= date, y = data), colour = "red" )+
  xlab("Month") + 
  ylab("Deaths reported")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  ggtitle("Fitting the ARIMA model with existing data") +
  color_theme()

ggplotly(fit_existing_dth_plot)
NA

Step 6 Forecast using the model

Data Preparation :

forecast_dth_model <- forecast(auto_arima_fit_death,level = c(80, 95), h = 30) 

#Convert the model to dataframe for plotting

# Negative values of the CI interval are considered as 0

forecast_dth_model_data <- fortify(forecast_dth_model) %>% 
  clean_names() %>% 
  mutate(data = round(data,2),
         fitted= round(fitted,2))  %>% 
  mutate (lo_80 = ifelse(lo_80 < 0,0,lo_80),
          lo_95 = ifelse(lo_95 < 0,0,lo_95)
          )

forecast_start_date <- as.Date(max(daily_death_hb_timeseries_data$date)+1)
forecast_end_date <- as.Date(forecast_start_date+29)

forecast_dth_data <- forecast_dth_model_data %>% 
  filter(!(is.na(point_forecast))) %>% 
  mutate(date = seq(forecast_start_date,forecast_end_date, by =1)) %>% 
select(-data,-fitted, -index)  

fitted_dth_data <- forecast_dth_model_data %>% 
  filter(!(is.na(data))) %>% 
  inner_join(daily_death_hb_timeseries_data, by = c("index")) %>% 
  mutate(date = as.Date(date)) %>% 
select(date, data, fitted) 

Plotting the Vaccination series plus the forecast and 80 - 95% prediction intervals


annotation <- data.frame(
   x = c(as.Date("13-06-2021","%d-%m-%Y"),as.Date("11-10-2021","%d-%m-%Y")),
   y = c(30,40),
   label = c("PAST", "FUTURE")
)

#Time series plots for the next 60 days according to best ARIMA models with 80%–95% CI.
forecast_data_dth_plot <- fitted_dth_data %>% 
  ggplot()+
  geom_line(aes(x= date, y = data), color = "#5ab4ac")+
  #geom_line(aes(x= date, y = fitted), colour = "red" )+
  geom_line(aes(x= date, y =point_forecast), color ="blue", data = forecast_dth_data )+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_80, ymax = hi_80), 
              data = forecast_dth_data, alpha = 0.3, fill = "green")+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_95, ymax = hi_95), 
              data = forecast_dth_data, alpha = 0.1)+
  geom_vline(aes(xintercept=as.numeric(max(date))),color="#f1a340", linetype="dashed",data = fitted_dth_data)+
  ggtitle("Projection of new Deaths") +
  xlab("Month") + 
  ylab("Death reported")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  color_theme()+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  geom_text(data=annotation, 
            aes( x=x, y=y, label=label),                  
            color="blue", 
            size=4 )
   

ggplotly(forecast_data_dth_plot )
LS0tDQp0aXRsZTogIlBIU19DT1ZJRF9BTEwiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KI0xvYWQgdGhlIGRhdGEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShmYWJsZSkNCmxpYnJhcnkoZmFibGV0b29scykNCmxpYnJhcnkodHNpYmJsZSkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeSh0c2VyaWVzKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkoem9vKQ0KbGlicmFyeShkbG0pDQpsaWJyYXJ5KHVyY2EpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShhc3RzYSkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KDQojTG9hZCB0aGUgZGF0YS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0Kc291cmNlKGhlcmUoImNsZWFuaW5nX3NjcmlwdHMvcGhzX2NvdmlkX2NsZWFuaW5nLlIiKSkNCnNvdXJjZShoZXJlKCJhbmFseXNpcy9waHNfY292aWRfZnVuY3Rpb25zLlIiKSkNCmBgYA0KDQojIyMgKioqUGxvdDEoYSk6IFRyZW5kIG9uIHBlb3BsZSB3aG8gdGVzdGVkIHBvc2l0aXZlKioqDQoNCmBgYHtyfQ0KI0NhbGN1bGF0ZSByb2xsaW5nIGF2ZXJhZ2UgZm9yIEhlYWx0aCBib2FyZCBlcXVhbHMgU2NvdGxhbmQgICANCnRyZW5kX2hiX2RhaWx5X3JvbGxfYXZnIDwtIGNhbGN1bGF0ZV9yb2xsX2F2ZygicG9zaXRpdmUiKQ0KDQojY29udmVydCB0aGUgZGF0YSBmcmFtZSB0byB0aW1lIHNlcmllcyB0byB1c2UgdGhlIHJhbmdlIHNsaWRlciBhbmQgc2VsZWN0b3INCnRyZW5kX2hiX2RhaWx5X3JvbGxfYXZnX3RzIDwtIGNvbnZlcnRfdG9fdGltZXNlcmllcyh0cmVuZF9oYl9kYWlseV9yb2xsX2F2ZykNCg0KIyBDcmVhdGUgdGhlIHBsb3QgdXNpbmcgcGxvdF9seQ0KdHJlbmRfcG9zaXRpdmVfcGxvdCA8LSBwbG90X2x5KHRyZW5kX2hiX2RhaWx5X3JvbGxfYXZnX3RzLCB4ID0gfkRhdGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGhvdmVyaW5mbyA9InRleHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJEYXRlOiAiLCBEYXRlLCI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRGFpbHkgUG9zaXRpdmU6ICIsIGRhaWx5X3Bvc2l0aXZlLCI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2V2ZW4gRGF5IEF2ZXJhZ2U6ICIsc2V2ZW5fZGF5X3JvbGxfYXZnKSkgIA0KI2FkZCBiYXIgY2hhcnQgZm9yIGRhaWx5IHBvc2l0aXZlDQp0cmVuZF9wb3NpdGl2ZV9wbG90IDwtIHRyZW5kX3Bvc2l0aXZlX3Bsb3QgJT4lIGFkZF9iYXJzKHkgPSB+ZGFpbHlfcG9zaXRpdmUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiRGFpbHkgUG9zaXRpdmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yICA9ICBJKCIjNWFiNGFjIikgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCiNhZGQgbGluZSBmb3Igcm9sbGluZyBhdmVyYWdlDQp0cmVuZF9wb3NpdGl2ZV9wbG90IDwtIHRyZW5kX3Bvc2l0aXZlX3Bsb3QgJT4lIGFkZF9saW5lcyh5ID0gfnNldmVuX2RheV9yb2xsX2F2ZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJTZXZlbiBEYXkgUm9sbGluZyBBdmVyYWdlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBJKCJyZWQiKSkgDQojYWRkIGxheW91dCB0byB0aGUgZXhpc3RpbmcgcGxvdA0KdHJlbmRfcG9zaXRpdmVfcGxvdCA8LSB0cmVuZF9wb3NpdGl2ZV9wbG90ICU+JSBsYXlvdXQoDQogICAgdGl0bGUgPSBsaXN0KHRleHQgPSJUcmVuZCBvbiBQb3NpdGl2ZSBDYXNlcyIpLA0KICAgIHhheGlzID0geGF4aXNfbGlzdCwNCiAgICB5YXhpcyA9IHlheGlzX2xpc3QsDQogICAgbGVnZW5kID0gbGlzdCh0aXRsZT1saXN0KHRleHQ9JzxiPiBUcmVuZCA8L2I+JyksDQogICAgICAgICAgICAgICAgICB4ID0gMC4xLCB5ID0gMC45KSkNCiAgICAgDQp0cmVuZF9wb3NpdGl2ZV9wbG90DQpgYGANCg0KRGF0YSBmb3IgdGhlIHJlcG9ydCAoUHJlc2VudGF0aW9uKQ0KDQpgYGB7cn0NCnRvdGFsX3Bvc2l0aXZlX2Nhc2UgPC0gdHJlbmRfaGJfZGFpbHkgJT4lIA0KICBmaWx0ZXIoaGJfbmFtZSA9PSAiU2NvdGxhbmQiKSAlPiUgDQogIGZpbHRlcihkYXRlID09IG1heChkYXRlKSkNCg0KcmVjZW50XzdkYXlzIDwtIGNhbGN1bGF0ZV9yb2xsX2F2ZygicG9zaXRpdmUiKSAlPiUgDQogIGZpbHRlciAoZGF0ZSA+PSBtYXgoZGF0ZSktNykgJT4lIA0KICBzdW1tYXJpc2UodG90YWwgPSBzdW0oZGFpbHlfcG9zaXRpdmUpKQ0KDQpwcmV2aW91c183ZGF5cyA8LSBjYWxjdWxhdGVfcm9sbF9hdmcoInBvc2l0aXZlIikgJT4lIA0KICBmaWx0ZXIgKGRhdGUgPj0gbWF4KGRhdGUpLTE0KSAlPiUgDQogIGZpbHRlciAoZGF0ZSA8IG1heChkYXRlKS03KSAlPiUgDQogIHN1bW1hcmlzZSh0b3RhbCA9IHN1bShkYWlseV9wb3NpdGl2ZSkpDQoNCnBlcmNlbnRhZ2VfY2FsYyA9IChyZWNlbnRfN2RheXMgLXByZXZpb3VzXzdkYXlzICkvcHJldmlvdXNfN2RheXMgKiAxMDAgDQoNCnBlcmNlbnRhZ2VfY2FsYw0KIyBkZWNyZWFzZSBvZiAxMy4wNCAlDQoNCnRvdGFsX2hvc3BpdGFsIDwtIHRyZW5kX2hiX2RhaWx5ICU+JSANCiAgZmlsdGVyKGhiX25hbWUgPT0gIlNjb3RsYW5kIikgJT4lIA0KICBzdW1tYXJpc2UodG90YWwgPSBzdW0oaG9zcGl0YWxfYWRtaXNzaW9ucywgbmEucm0gPSBUUlVFKSkNCg0KcmVjZW50XzdkYXlzX2hvc3BpdGFsIDwtIGNhbGN1bGF0ZV9yb2xsX2F2ZygiaG9zcGl0YWwiKSAlPiUgDQogIGZpbHRlciAoIShpcy5uYShzZXZlbl9kYXlfcm9sbF9hdmcpKSkgJT4lIA0KICBmaWx0ZXIgKGRhdGUgPj0gbWF4KGRhdGUpLTcpICU+JSANCiAgc3VtbWFyaXNlKHRvdGFsID0gc3VtKGhvc3BpdGFsX2FkbWlzc2lvbnMpKQ0KDQpwcmV2aW91c183ZGF5c19ob3NwaXRhbCA8LSBjYWxjdWxhdGVfcm9sbF9hdmcoImhvc3BpdGFsIikgJT4lIA0KICBmaWx0ZXIgKCEoaXMubmEoc2V2ZW5fZGF5X3JvbGxfYXZnKSkpICU+JSANCiAgZmlsdGVyIChkYXRlID49IG1heChkYXRlKS0xNCkgJT4lIA0KICBmaWx0ZXIgKGRhdGUgPCBtYXgoZGF0ZSktNykgJT4lIA0KICBzdW1tYXJpc2UodG90YWwgPSBzdW0oaG9zcGl0YWxfYWRtaXNzaW9ucykpDQoNCnBlcmNlbnRhZ2VfY2FsY19ob3NwID0gKHJlY2VudF83ZGF5c19ob3NwaXRhbCAtcHJldmlvdXNfN2RheXNfaG9zcGl0YWwgKS9wcmV2aW91c183ZGF5c19ob3NwaXRhbCAqIDEwMCANCg0KcGVyY2VudGFnZV9jYWxjX2hvc3ANCiMgZGVjcmVhc2Ugb2YgMTUuMTEgJQ0KDQoNCmljdV9ob3NwaXRhbCA8LSB0cmVuZF9oYl9kYWlseSAlPiUgDQogIGZpbHRlcihoYl9uYW1lID09ICJTY290bGFuZCIpICU+JSANCiAgc3VtbWFyaXNlKHRvdGFsID0gc3VtKGljdV9hZG1pc3Npb25zLCBuYS5ybSA9IFRSVUUpKQ0KDQpjdW1fZGVhdGhzIDwtIHRyZW5kX2hiX2RhaWx5ICU+JSANCiAgZmlsdGVyKGhiX25hbWUgPT0gIlNjb3RsYW5kIikgJT4lIA0KICBmaWx0ZXIgKGRhdGUgPT0gbWF4KGRhdGUpKSAlPiUgDQogIHNlbGVjdCAoY3VtdWxhdGl2ZV9kZWF0aHMpDQoNCmRhaWx5X3ZhY2NfaGIgJT4lIA0KICBmaWx0ZXIoaGJfbmFtZSA9PSAiU2NvdGxhbmQiLA0KICAgICAgICAgc2V4ID09ICJUb3RhbCIsDQogICAgICAgICBhZ2VfZ3JvdXAgPT0gIkFsbCB2YWNjaW5hdGlvbnMiKSAlPiUgDQogIGZpbHRlciAoZG9zZSA9PSAiRG9zZSAxIikgJT4lIA0KICBmaWx0ZXIgKGRhdGUgPT0gbWF4KGRhdGUpKQ0KYGBgDQoNCiMjIyAqKipQbG90MShiKTogVG90YWwgUG9zaXRpdmUgQ2FzZSBieSBuZWlnaGJvcmhvb2QgKEJhc2VkIG9uIExvY2FsIEF1dGhvcml0eSkuKioqDQoNCmBgYHtyfQ0KDQp0cmVuZF9sYV9kYWlseV9uYiA8LSBjYWxjdWxhdGVfcm9sbF9hdmcoImxhX25laWdoYm91ciIpDQoNCiMgR2V0IHRoZSBwb3B1bGF0aW9uIGRhdGEgIA0KbGFfcG9wdWxhdGlvbiA8LXRyZW5kX3NldmVuX2RheSAlPiUgDQogIGZpbHRlcihkYXRlID09IG1heChkYXRlKSkgJT4lIA0KICBncm91cF9ieSAoY2EpICU+JSANCiAgc3VtbWFyaXNlKHRvdGFsX3BvcHVsYXRpb24gPSBzdW0ocG9wdWxhdGlvbikpDQoNCiMgUHJlcGFyZSB0aGUgbWFwIGRhdGEgYnkgY29tYmluaW5nIHNwYXRpYWwgZGF0YSBhbmQgKGxvY2FsIGF1dGhvcml0eSkgbGEgZGF0YSAgDQptYXBfZGF0YSA8LSB6b25lc19sYSAlPiUgDQogICAgaW5uZXJfam9pbih0cmVuZF9sYV9kYWlseV9uYiwgYnkgPSBjKCJjb2RlIiA9ICJjYSIpKSU+JSANCiAgICBpbm5lcl9qb2luKGxhX3BvcHVsYXRpb24sIGJ5ID0gYygiY29kZSIgPSAiY2EiKSkgJT4lIA0KICBhcnJhbmdlKHNldmVuX2RheV9yb2xsX2F2ZykNCg0KIyBDcmVhdGUgYSBtYXAgcGFsZXR0ZSAgDQptYXBfcGFsZXR0ZSA8LSBjb2xvck51bWVyaWMoInBsYXNtYSIsIGRvbWFpbiA9IHJhbmdlKG1hcF9kYXRhJHNldmVuX2RheV9yb2xsX2F2ZykpDQptYXBfZGF0YSA8LSBtYXBfZGF0YSAlPiUgDQogICAgbXV0YXRlKGNvbG91ciA9IG1hcF9wYWxldHRlKHNldmVuX2RheV9yb2xsX2F2ZykpDQoNCiMgRGlzcGxheSB0aGUgc3BhdGlhbCBkYXRhDQptYXBfZGF0YSAlPiUNCiAgICBsZWFmbGV0KCkgJT4lDQogICAgYWRkUG9seWdvbnMoDQogICAgIHBvcHVwID0gfiBzdHJfYygiPGI+PGgyPiIsbmFtZSwgICI8L2gyPiIsIA0KICAgICAgICAgICAgICAgICAgICAgICI8aDM+KDAxLU9jdC0yMDExIHRvIDA3LU9jdC0yMDIxKTwvaDM+PC9iPiIsIA0KICAgICAgICAgICAgICAgICAgICAgICI8Yj5OdW1iZXIgb2YgUG9zaXRpdmUgQ2FzZXMgb3ZlciA3IGRheXM6IDwvYj4iLCBzZXZlbl9kYXlfcm9sbF9hdmcsDQogICAgICAgICAgICAgICAgICAgICAiIDxicj48Yj43IGRheSByYXRlIHBlciAxMDAsMDAwIHBlb3BsZTogPC9iPiIsIGNydWRlX3JhdGU3ZGF5X3Bvc2l0aXZlLA0KICAgICAgICAgICAgICAgICAgICAgIiA8YnI+PGJyPjxiPlRvdGFsIFBvcHVsYXRpb246IDwvYj4iLCB0b3RhbF9wb3B1bGF0aW9uLA0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpLA0KICAgICAgY29sb3IgPSB+Y29sb3VyDQogICAgKSAlPiUNCiAgICBhZGRMZWdlbmQoDQogICAgICBwb3NpdGlvbiA9ICJ0b3ByaWdodCIsDQogICAgICBjb2xvcnMgPSB+Y29sb3VyLA0KICAgICAgbGFiZWxzID0gfm5hbWUNCiAgICApDQoNCmBgYA0KDQojIyMgKioyIEFuYWx5c2UgdGhlIHRyZW5kIG9uIEhvc3BpdGFsaXphdGlvbnM6KioNCg0KYGBge3J9DQojQ2FsY3VsYXRlIHJvbGxpbmcgYXZlcmFnZSBmb3IgSGVhbHRoIGJvYXJkIGVxdWFscyBTY290bGFuZCAgIA0KdHJlbmRfaGJfaG9zcF9yb2xsX2F2ZyA8LSBjYWxjdWxhdGVfcm9sbF9hdmcoImhvc3BpdGFsIikNCg0KI2NvbnZlcnQgdGhlIGRhdGEgZnJhbWUgdG8gdGltZSBzZXJpZXMgdG8gdXNlIHRoZSByYW5nZSBzbGlkZXIgYW5kIHNlbGVjdG9yDQp0cmVuZF9oYl9ob3NwX3JvbGxfYXZnX3RzIDwtIGNvbnZlcnRfdG9fdGltZXNlcmllcyh0cmVuZF9oYl9ob3NwX3JvbGxfYXZnKQ0KDQojIENyZWF0ZSB0aGUgcGxvdCB1c2luZyBwbG90X2x5DQp0cmVuZF9ob3NwaXRhbF9wbG90IDwtIHBsb3RfbHkodHJlbmRfaGJfaG9zcF9yb2xsX2F2Z190cywgeCA9IH5EYXRlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBob3ZlcmluZm8gPSJ0ZXh0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IH5wYXN0ZSgiRGF0ZTogIiwgRGF0ZSwiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBhdGllbnRzIGFkbWl0dGVkOiAiLCBob3NwaXRhbF9hZG1pc3Npb25zLCI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2V2ZW4gRGF5IEF2ZXJhZ2U6ICIsc2V2ZW5fZGF5X3JvbGxfYXZnKSkgIA0KI2FkZCBiYXIgY2hhcnQgZm9yIGRhaWx5IHBvc2l0aXZlDQp0cmVuZF9ob3NwaXRhbF9wbG90IDwtIHRyZW5kX2hvc3BpdGFsX3Bsb3QgJT4lIGFkZF9iYXJzKHkgPSB+aG9zcGl0YWxfYWRtaXNzaW9ucywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJQYXRpZW50cyBhZG1pdHRlZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgID0gIEkoIiM1YWI0YWMiKSAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KI2FkZCBsaW5lIGZvciByb2xsaW5nIGF2ZXJhZ2UNCnRyZW5kX2hvc3BpdGFsX3Bsb3QgPC0gdHJlbmRfaG9zcGl0YWxfcGxvdCAlPiUgYWRkX2xpbmVzKHkgPSB+c2V2ZW5fZGF5X3JvbGxfYXZnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlNldmVuIERheSBSb2xsaW5nIEF2ZXJhZ2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IEkoInJlZCIpKSANCiNhZGQgbGF5b3V0IHRvIHRoZSBleGlzdGluZyBwbG90DQp0cmVuZF9ob3NwaXRhbF9wbG90IDwtIHRyZW5kX2hvc3BpdGFsX3Bsb3QgJT4lIGxheW91dCgNCiAgICB0aXRsZSA9IGxpc3QodGV4dCA9Ikhvc3BpdGFsaXNhdGlvbiIpLA0KICAgIHhheGlzID0geGF4aXNfbGlzdCwNCiAgICB5YXhpcyA9IHlheGlzX2xpc3QsDQogICAgbGVnZW5kID0gbGlzdCh0aXRsZT1saXN0KHRleHQ9JzxiPiBUcmVuZCA8L2I+JyksDQogICAgICAgICAgICAgICAgICB4ID0gMC4xLCB5ID0gMC45KSkNCiAgICAgDQp0cmVuZF9ob3NwaXRhbF9wbG90DQoNCmBgYA0KDQojIyMgKioyIEFuYWx5c2UgdGhlIHRyZW5kIG9uIERlYXRoczoqKg0KDQpgYGB7cn0NCiNDYWxjdWxhdGUgcm9sbGluZyBhdmVyYWdlIGZvciBIZWFsdGggYm9hcmQgZXF1YWxzIFNjb3RsYW5kICAgDQp0cmVuZF9oYl9kZWF0aF9yb2xsX2F2ZyA8LSBjYWxjdWxhdGVfcm9sbF9hdmcoImRlYXRoIikNCg0KI2NvbnZlcnQgdGhlIGRhdGEgZnJhbWUgdG8gdGltZSBzZXJpZXMgdG8gdXNlIHRoZSByYW5nZSBzbGlkZXIgYW5kIHNlbGVjdG9yDQp0cmVuZF9oYl9kZWF0aF9yb2xsX2F2Z190cyA8LSBjb252ZXJ0X3RvX3RpbWVzZXJpZXModHJlbmRfaGJfZGVhdGhfcm9sbF9hdmcpDQoNCiMgQ3JlYXRlIHRoZSBwbG90IHVzaW5nIHBsb3RfbHkNCnRyZW5kX2RlYXRoX3Bsb3QgPC0gcGxvdF9seSh0cmVuZF9oYl9kZWF0aF9yb2xsX2F2Z190cywgeCA9IH5EYXRlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBob3ZlcmluZm8gPSJ0ZXh0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IH5wYXN0ZSgiRGF0ZTogIiwgRGF0ZSwiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRhaWx5IERlYXRoczogIiwgZGFpbHlfZGVhdGhzLCI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU2V2ZW4gRGF5IEF2ZXJhZ2U6ICIsc2V2ZW5fZGF5X3JvbGxfYXZnKSkgIA0KI2FkZCBiYXIgY2hhcnQgZm9yIGRhaWx5IHBvc2l0aXZlDQp0cmVuZF9kZWF0aF9wbG90IDwtIHRyZW5kX2RlYXRoX3Bsb3QgJT4lIGFkZF9iYXJzKHkgPSB+ZGFpbHlfZGVhdGhzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkRhaWx5IERlYXRocyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgID0gIEkoIiM1YWI0YWMiKSAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KI2FkZCBsaW5lIGZvciByb2xsaW5nIGF2ZXJhZ2UNCnRyZW5kX2RlYXRoX3Bsb3QgPC0gdHJlbmRfZGVhdGhfcGxvdCAlPiUgYWRkX2xpbmVzKHkgPSB+c2V2ZW5fZGF5X3JvbGxfYXZnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlNldmVuIERheSBSb2xsaW5nIEF2ZXJhZ2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IEkoInJlZCIpKSANCiNhZGQgbGF5b3V0IHRvIHRoZSBleGlzdGluZyBwbG90DQp0cmVuZF9kZWF0aF9wbG90IDwtIHRyZW5kX2RlYXRoX3Bsb3QgJT4lIGxheW91dCgNCiAgICB0aXRsZSA9IGxpc3QodGV4dCA9IkRlYXRocyByZXBvcnRlZCIpLA0KICAgIHhheGlzID0geGF4aXNfbGlzdCwNCiAgICB5YXhpcyA9IHlheGlzX2xpc3QsDQogICAgbGVnZW5kID0gbGlzdCh0aXRsZT1saXN0KHRleHQ9JzxiPiBUcmVuZCA8L2I+JyksDQogICAgICAgICAgICAgICAgICB4ID0gMC4xLCB5ID0gMC45KSkNCiAgICAgDQp0cmVuZF9kZWF0aF9wbG90DQoNCmBgYA0KDQoiUEhTX0NPVklEIFZhY2NpbmF0aW9uIFByZWRpY3Rpb24gTW9kZWwgVXNpbmcgQVJJTUEiDQoNCiMjIyBEYXRhIFByZXBhcmF0aW9uDQoNCmBgYHtyfQ0KDQp0cmVuZF92YWNjX2hiIDwtIGRhaWx5X3ZhY2NfaGIgJT4lIA0KICBmaWx0ZXIoaGJfbmFtZSA9PSAiU2NvdGxhbmQiKSAlPiUgDQogIGZpbHRlcihzZXggPT0iVG90YWwiKSAlPiUgDQogIGZpbHRlcihhZ2VfZ3JvdXAgPT0gIkFsbCB2YWNjaW5hdGlvbnMiKSAlPiUgDQogIGZpbHRlcihjdW11bGF0aXZlX251bWJlcl92YWNjaW5hdGVkIT0wKSANCg0KYGBgDQoNCiMjICoqQW5hbHlzZSB0aGUgdHJlbmQgb24gVmFjY2luYXRpb25zOioqDQoNCiMjIyAqKipQbG90MShhKTogVHJlbmQgb24gVmFjY2luYXRpb24qKioNCg0KYGBge3J9DQojUGxvdCB0byB2aXN1YWxpemUgdHJlbmQgb24gdmFjY2luYXRpb24uDQpwbG90X3ZhY2NpbmUgPC0gdHJlbmRfdmFjY19oYiAlPiUgDQogIGdncGxvdCgpKw0KICBhZXMoeCA9IGRhdGUsIHkgPSBudW1iZXJfdmFjY2luYXRlZCkrDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBkb3NlKSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJQZW9wbGUgVmFjY2luYXRlZCIpICsNCiAgeGxhYigiWWVhciIpICsNCiAgeWxhYigiTm8gb2YgUG9zaXRpdmUgQ2FzZXMiKSArDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpDQoNCmdncGxvdGx5KHBsb3RfdmFjY2luZSkNCmBgYA0KDQojIyMgKioqUGxvdDEoYik6IEN1bXVsYXRpdmUgVG90YWwgb24gVmFjY2luYXRpb24qKioNCg0KYGBge3J9DQojIElkZW50aWZ5IHRoZSBwb3B1bGF0aW9uDQpkb3NlX3BvcHVsYXRpb24gPC0gZGFpbHlfdmFjY19oYiAlPiUgDQogIGZpbHRlcihzZXggPT0gIlRvdGFsIikgJT4lIA0KICBmaWx0ZXIoZGF0ZSA9PSBtYXgoZGF0ZSkpICU+JSANCiAgZmlsdGVyKGhiX25hbWUgPT0iU2NvdGxhbmQiKSAlPiUgDQogIGZpbHRlcihhZ2VfZ3JvdXAgPT0iMTYgeWVhcnMgYW5kIG92ZXIiKSAlPiUgDQogIHNlbGVjdChwb3B1bGF0aW9uKSAlPiUgDQogIGRpc3RpbmN0KCkNCg0KI1Bsb3QgdG8gdmlzdWFsaXNlIGN1bXVsYXRpdmUgdmFjY2luYXRpb24gdHJlbmQuICANCnBsb3RfdmFjY2luZV9jdW1tIDwtIHRyZW5kX3ZhY2NfaGIgJT4lIA0KICBnZ3Bsb3QoKSsNCiAgYWVzKHggPSBkYXRlLCB5ID0gY3VtdWxhdGl2ZV9udW1iZXJfdmFjY2luYXRlZCkrDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBkb3NlKSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJDdW1tdWxhdGl2ZSBUcmVuZCBvbiBWYWNjaW5hdGlvbiIpICsNCiAgeGxhYigiWWVhciIpICsNCiAgeWxhYigiTm8gb2YgUGVvcGxlIFZhY2NpbmF0ZWQiKSArDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIk0iLCBzY2FsZSA9IDFlLTYpLCANCiAgICAgICAgICAgICAgICAgICAgIHNlYy5heGlzID0gc2VjX2F4aXModHJhbnMgPSB+Li9kb3NlX3BvcHVsYXRpb24kcG9wdWxhdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGVyY2VudGFnZSIsDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAwLjAxKQ0KICAgICAgICAgICAgICAgICAgICApKQ0KDQpwbG90bHk6OmdncGxvdGx5KHBsb3RfdmFjY2luZV9jdW1tKQ0KDQpgYGANCg0KIyMjICoqKlBsb3QxKGMpOiBDdW11bGF0aXZlIFRvdGFsIG9uIFZhY2NpbmF0aW9uIGJhc2VkIG9uIEFnZSBncm91cCoqKg0KDQpgYGB7cn0NCmN1bW1fdmFjX2FnZTwtIGRhaWx5X3ZhY2NfaGIgJT4lIA0KICBmaWx0ZXIoc2V4ID09ICJUb3RhbCIpICU+JSANCiAgZmlsdGVyKGlzLm5hKGFnZV9ncm91cF9xZikpICU+JSANCiAgZmlsdGVyKGRhdGUgPT0gbWF4KGRhdGUpKSAlPiUgDQogIGZpbHRlcihoYl9uYW1lID09IlNjb3RsYW5kIikgJT4lIA0KICBmaWx0ZXIoYWdlX2dyb3VwICE9IkFsbCB2YWNjaW5hdGlvbnMiKSAlPiUgDQogIG11dGF0ZSAoY3VtdWxhdGl2ZV9wZXJjZW50X2NvdmVyYWdlID0gaWZlbHNlKGN1bXVsYXRpdmVfcGVyY2VudF9jb3ZlcmFnZSA+MTAwLCAxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGN1bXVsYXRpdmVfcGVyY2VudF9jb3ZlcmFnZSwyKSkpICU+JSANCiAgc2VsZWN0KGRvc2UsYWdlX2dyb3VwLCBjdW11bGF0aXZlX3BlcmNlbnRfY292ZXJhZ2UsIHBvcHVsYXRpb24pDQogIA0KY3VtbV92YWNfYWdlX3Bsb3QgPC0gY3VtbV92YWNfYWdlICU+JSANCiAgZ2dwbG90KCkrDQogIGFlcyh4ID0gYWdlX2dyb3VwLCB5ID0gY3VtdWxhdGl2ZV9wZXJjZW50X2NvdmVyYWdlKSsNCiAgZ2VvbV9jb2woYWVzKGZpbGwgPSBkb3NlKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICMgZ2VvbV90ZXh0KGFlcyhsYWJlbD1jdW11bGF0aXZlX3BlcmNlbnRfY292ZXJhZ2UsIGhqdXN0ID0gMCksDQogICAjICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSh3aWR0aD0wLjkpLGFuZ2xlID0gOTApKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBDb3ZlcmFnZSBvZiBWYWNjaW5hdGlvbiBieSBhZ2UgZ3JvdXAiKSArDQogIHhsYWIoIkFnZSBncm91cCIpICsNCiAgeWxhYigiJSBvZiBDb3ZlcmFnZSIpICsNCiAgY29sb3JfdGhlbWUoKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpDQoNCmdncGxvdGx5KGN1bW1fdmFjX2FnZV9wbG90KQ0KDQojcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9MC45KSwgdmp1c3Q9LTAuMjUNCg0KYGBgDQoNCiMgRm9yZWNhc3Qgb24gVmFjY2luYXRpb246IEFSSU1BIE1vZGVsDQoNCioqRGF0YSBQcmVwYXJhdGlvbioqDQoNCmBgYHtyfQ0KdHJlbmRfdmFjY19oYiA8LSB0cmVuZF92YWNjX2hiICU+JSANCiAgZmlsdGVyIChkb3NlID09ICJEb3NlIDIiKSAlPiUgDQogIHNlbGVjdChkYXRlLGN1bXVsYXRpdmVfbnVtYmVyX3ZhY2NpbmF0ZWQpDQoNCiMgQ29udmVydCBpdCB0byB6b28gdHlwZQ0KZGFpbHlfdmFjY19oYl96b28gPC0gem9vKHRyZW5kX3ZhY2NfaGIkY3VtdWxhdGl2ZV9udW1iZXJfdmFjY2luYXRlZCwgDQogICAgICAgICAgIG9yZGVyLmJ5PWFzLkRhdGUodHJlbmRfdmFjY19oYiRkYXRlLCBmb3JtYXQ9JyVtLyVkLyVZJykpDQoNCiMgQ29udmVydCBpdCBpbnRvIGEgdGltZSBzZXJpZXMNCmRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcyA8LXRpbWVTZXJpZXM6OmFzLnRpbWVTZXJpZXMoZGFpbHlfdmFjY19oYl96b28pDQoNCmBgYA0KDQojIyBTdGVwIDEgOiBWaXN1YWxpemUgdGhlIHRpbWUgc2VyaWVzDQoNCmBgYHtyfQ0Kb3JpZ2luYWxfc2VyaWVzPC0NCiAgYXV0b3Bsb3QoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBjb2xvdXIgPSAnIzVhYjRhYycpKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIlZBQ0NJTkFURUQiKSsNCiAgI3NjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJPcmlnaW5hbCBTZXJpZXMiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnVuaXRfZm9ybWF0KHVuaXQgPSAiTSIsIHNjYWxlID0gMWUtNikpKw0KICBjb2xvcl90aGVtZSgpDQoNCmdncGxvdGx5KG9yaWdpbmFsX3NlcmllcykNCmBgYA0KDQojIyBTdGVwIDIgOiBJZGVudGlmaWNhdGlvbiBvZiBtb2RlbCA6IChGaW5kaW5nIGQ6KQ0KDQpJZGVudGlmeSB3aGV0aGVyIHRoZSB0aW1lIHNlcmllcyBpcyBzdGF0aW9uYXJ5IC8gbm9uIHN0YXRpb25hcnkgd2UgY2FuIHVzZSBBREYgQXVnbWVudGVkIERpY2tleS1GdWxsZXIgdGVzdA0KDQpgYGB7cn0NCmFkZl90ZXN0IDwtIGFkZi50ZXN0KGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcykNCmBgYA0KDQpUaGUgdGltZSBzZXJpZXMgaXMgbm90IHN0YXRpb25hcnkgc2luY2Ugd2UgaGF2ZSBhIGhpZ2ggcC12YWx1ZS4NClNvIHdlIGFwcGx5IGRpZmZlcmVuY2UNCg0KYGBge3J9DQpmaXJzdF9kaWZmX3RzPC0gZGlmZihkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpDQphZGZfdGVzdDEgPC0gYWRmLnRlc3QobmEub21pdChmaXJzdF9kaWZmX3RzKSkNCnNlY29uZF9kaWZmX3RzPC0gZGlmZihmaXJzdF9kaWZmX3RzKQ0KYWRmX3Rlc3QyIDwtIGFkZi50ZXN0KG5hLm9taXQoc2Vjb25kX2RpZmZfdHMpKQ0KDQphZGZfdGVzdDENCmFkZl90ZXN0Mg0KYGBgDQoNCkNyZWF0ZSBhIGRhdGFmcmFtZSB0byBjb21wYXJlDQoNCmBgYHtyfQ0KYWRmX2RhdGEgPC0gZGF0YS5mcmFtZShEYXRhID0gYygiT3JpZ2luYWwiLCAiRmlyc3QtT3JkZXJlZCIsICJTZWNvbmQgT3JkZXJlZCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBEaWNrZXlfRnVsbGVyID0gYyhhZGZfdGVzdCRzdGF0aXN0aWMsIGFkZl90ZXN0MSRzdGF0aXN0aWMsIGFkZl90ZXN0MiRzdGF0aXN0aWMpLA0KICAgICAgICAgICAgICAgICAgICAgICBwX3ZhbHVlID0gYyhhZGZfdGVzdCRwLnZhbHVlLGFkZl90ZXN0MSRwLnZhbHVlLGFkZl90ZXN0MiRwLnZhbHVlKSkNCmFkZl9kYXRhDQpgYGANCg0KSW5pdGlhbGx5IHRoZSBwLXZhbHVlIGlzIGhpZ2ggd2hpY2ggaW5kaWNhdGVzIHRoYXQgdGhlIFRpbWUgU2VyaWVzIGlzIG5vdCBzdGF0aW9uYXJ5Lg0KU28gd2UgYXBwbHkgZGlmZmVyZW5jZSAyIHRpbWVzLg0KQWZ0ZXIgdGhlIHNlY29uZCBkaWZmZXJlbmNlLCB0aGUgcC12YWx1ZSBcPCBzaWduaWZpY2FuY2UgbGV2ZWwgKDAuMDUpIFNvIHdlIGNhbiBjb25jbHVkZSB0aGF0IHRoZSBkaWZmZXJlbmNlIGRhdGEgYXJlIHN0YXRpb25hcnkuDQpTbyBkaWZmZXJlbmNlIChkID0gMikNCg0KT3RoZXIgbWV0aG9kIHRvIGNvbmZpcm0NCg0KYGBge3J9DQpuZGlmZnMoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzKQ0KYGBgDQoNCkxldCdzIHBsb3QgdGhlIEZpcnN0IE9yZGVyIGFuZCBTZWNvbmQgT3JkZXIgRGlmZmVyZW5jZSBTZXJpZXMNCg0KT3JkZXIgb2YgZmlyc3QgZGlmZmVyZW5jZQ0KDQpgYGB7cn0NCg0KZmlyc3Rfb3JkZXI8LSBhdXRvcGxvdChmaXJzdF9kaWZmX3RzLCB0cy5jb2xvdXIgPSAnIzVhYjRhYycpICsNCiAgeGxhYigiTW9udGgiKSArIA0KICB5bGFiKCJWQUNDSU5BVEVEIikrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJGaXJzdC1PcmRlciBEaWZmZXJlbmNlIikgKw0KICBjb2xvcl90aGVtZSgpDQoNCmdncGxvdGx5KGZpcnN0X29yZGVyKQ0KYGBgDQoNCk9yZGVyIG9mIFNlY29uZCBkaWZmZXJlbmNlDQoNCmBgYHtyfQ0KDQpzZWNvbmRfb3JkZXI8LSBhdXRvcGxvdChzZWNvbmRfZGlmZl90cywgdHMuY29sb3VyID0gJyM1YWI0YWMnKSArDQogIHhsYWIoIk1vbnRoIikgKyANCiAgeWxhYigiVkFDQ0lOQVRFRCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiU2Vjb25kLU9yZGVyIERpZmZlcmVuY2UiKSArDQogIGNvbG9yX3RoZW1lKCkNCg0KZ2dwbG90bHkoc2Vjb25kX29yZGVyKQ0KYGBgDQoNCiMjIFN0ZXAgMyBFc3RpbWF0ZSB0aGUgcGFyYW1ldGVycyAoRmluZGluZyBwIGFuZCBxKQ0KDQpGb3Igb3VyIG1vZGVsIEFSSU1BIChwLGQscSksIHdlIGZvdW5kIGQgPSAyLCB0aGUgbmV4dCBzdGVwIGlzIHRvIGdldCB0aGUgdmFsdWVzIG9mIHAgYW5kIHEsIHRoZSBvcmRlciBvZiBBUiBhbmQgTUEgcGFydC4NClBsb3QgQUNGIGFuZCBQQUNGIGNoYXJ0cyB0byBpZGVudGlmeSBxIGFuZCBwIHJlc3BlY3RpdmVseS4NCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhcihtZnJvdz1jKDIsMikpDQogIGFjZjEoZmlyc3RfZGlmZl90cywgY29sPTI6NywgbHdkPTQpDQogIGFjZjEoc2Vjb25kX2RpZmZfdHMsIGNvbD0yOjcsIGx3ZD00KQ0KICBhY2YxKGZpcnN0X2RpZmZfdHMsIHBhY2YgPSBUUlVFLCBjb2w9Mjo3LCBsd2Q9NCkNCiAgYWNmMShzZWNvbmRfZGlmZl90cywgcGFjZiA9IFRSVUUsIGNvbD0yOjcsIGx3ZD00KQ0KYGBgDQoNClRoZSBBQ0YgYW5kIFBBQ0YgcGxvdHMgb2YgdGhlIGRpZmZlcmVuY2VkIGRhdGEgc2hvdyB0aGUgZm9sbG93aW5nIHBhdHRlcm5zOg0KDQpUaGUgQUNGIGRvZXNuJ3QgZm9sbG93IGEgc2ludXNvaWRhbCBwYXR0ZXJuIGJ1dCBpdHMgc2xvd2x5IGdlb21ldHJpYyBkZWNheS4NCkFsc28gdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgMyBpbiB0aGUgUEFDRiwgYnV0IG5vbmUgYmV5b25kIGxhZyAzLg0KU28gdGhlIGRhdGEgbWF5IGZvbGxvdyBhbiBBUigzKSBtb2RlbA0KDQpUaGUgUEFDRiBpcyBzaW51c29pZGFsIGFuZCBkZWNheWluZy4NCkFsc28gdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgMiBpbiB0aGUgQUNGLCBidXQgbm9uZSBiZXlvbmQgbGFnIDIgU28gdGhlIGRhdGEgbWF5IGZvbGxvdyBhbiBNQSgyKSBtb2RlbA0KDQpTbyB3ZSBwcm9wb3NlIHRocmVlIEFSTUEgbW9kZWxzIGZvciB0aGUgZGlmZmVyZW5jZWQgZGF0YTogQVJNQShwLHEpIEFSTUEoMywyKSwgQVJNQSgzLDApIGFuZCBBUk1BKDAsMikuDQoNClRoYXQgaXMsIGZvciB0aGUgb3JpZ2luYWwgdGltZSBzZXJpZXMsIHdlIHByb3Bvc2UgdGhyZWUgQVJJTUEgbW9kZWxzLEFSSU1BKHAsZCxxKSBBUklNQSgzLDEsMiksIEFSSU1BKDMsMSwwKSBhbmQgQVJNQSgzLDEsMikuDQoNCiMjIFN0ZXAgNCBCdWlsZCB0aGUgQVJJTUEgbW9kZWwNCg0KIyMjIE1hbnVhbCBBUklNQToNCg0KYGBge3J9DQphcmltYV9maXQxID0gQXJpbWEoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDIpKQ0KYXJpbWFfZml0MiA9IEFyaW1hKGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcywgb3JkZXIgPSBjKDMsMSwwKSkNCmFyaW1hX2ZpdDMgPSBBcmltYShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMsIG9yZGVyID0gYygzLDEsMikpDQphcmltYV9maXQ0ID0gQXJpbWEoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDEpKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShhcmltYV9maXQxKQ0Kc3VtbWFyeShhcmltYV9maXQyKQ0Kc3VtbWFyeShhcmltYV9maXQzKQ0Kc3VtbWFyeShhcmltYV9maXQ0KQ0KYGBgDQoNCkZvcmVjYXN0IHRoZSBNYW51YWwgQVJJTUEgbW9kZWwNCg0KYGBge3J9DQojIEZvcmVjYXN0IHRoZSBtYW51YWwgbW9kZWxzDQoNCmZ1dHVyZSA9IGZvcmVjYXN0KGFyaW1hX2ZpdDEsIGggPSAzMCkNCmZ1dHVyZTIgPSBmb3JlY2FzdChhcmltYV9maXQyLCBoID0gMzApDQpmdXR1cmUzID0gZm9yZWNhc3QoYXJpbWFfZml0MywgaCA9IDMwKQ0KZnV0dXJlNCA9IGZvcmVjYXN0KGFyaW1hX2ZpdDQsIGggPSAzMCkNCg0KI1Bsb3QgdGhlIGZvcmVjYXN0ZWQgbWFudWFsIG1vZGVscw0KDQpwYXIobWZyb3cgPSBjKDIsMikpDQpwbG90KGZ1dHVyZSkNCnBsb3QoZnV0dXJlMikNCnBsb3QoZnV0dXJlMykNCnBsb3QoZnV0dXJlNCkNCmBgYA0KDQojIyMgKipBdXRvbWF0ZWQgQVJJTUEqKg0KDQpgYGB7cn0NCmF1dG9fYXJpbWFfZml0X3ZhY2MgPC0gYXV0by5hcmltYShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMsDQogICAgICAgICAgICAgICAgICBzZWFzb25hbD1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIHN0ZXB3aXNlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICBhcHByb3hpbWF0aW9uID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICB0cmFjZSA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICkNCnN1bW1hcnkoYXV0b19hcmltYV9maXRfdmFjYykNCmBgYA0KDQoqKk1vZGVsIFNlbGVjdGlvbiBDcml0ZXJpYSA6KioNCg0KQVJJTUEgbW9kZWxzIHdpdGggbWluaW11bSBBSUMsIFJNU0UgYW5kIE1BUEUgY3JpdGVyaWEgd2VyZSBjaG9zZW4gYXMgdGhlIGJlc3QgbW9kZWxzLg0KQXV0b21hdGVkIEFSSU1BIGNvbmZpcm1zIHRoYXQgdGhlIEFSSU1BKDMsIDIsIDIpIHNlZW1zIGdvb2QgYmFzZWQgb24gQUlDDQoNCmBgYHtyfQ0KbG10ZXN0Ojpjb2VmdGVzdChhdXRvX2FyaW1hX2ZpdF92YWNjKQ0KYGBgDQoNCkFsbCB0aGUgY29lZmZpY2llbnRzIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50Lg0KDQojIyBTdGVwIDUgQ2hlY2sgZm9yIERpYWdub3N0aWNzDQoNCkxldCdzIHBsb3QgdGhlIGRpYWdub3N0aWNzIHdpdGggdGhlIHJlc3VsdHMgdG8gbWFrZSBzdXJlIHRoZSBub3JtYWxpdHkgYW5kIGNvcnJlbGF0aW9uIGFzc3VtcHRpb25zIGZvciB0aGUgbW9kZWwgaG9sZC4NCklmIHRoZSByZXNpZHVhbHMgbG9vayBsaWtlIHdoaXRlIG5vaXNlLCBwcm9jZWVkIHdpdGggZm9yZWNhc3QgYW5kIHByZWRpY3Rpb24sIG90aGVyd2lzZSByZXBlYXQgdGhlIG1vZGVsIGJ1aWxkaW5nLg0KDQpgYGB7cn0NCnJlcyA8LSBjaGVja3Jlc2lkdWFscyhhdXRvX2FyaW1hX2ZpdF92YWNjLCB0aGVtZSA9IGNvbG9yX3RoZW1lKCkpDQpyZXMNCmBgYA0KDQpUaGUgQUNGIHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSBBUklNQSgzLDIsMikgbW9kZWwgc2hvd3MgdGhhdCBhbG1vc3QgYXV0byBjb3JyZWxhdGlvbnN3aXRoIHJlZ3VsYXIgaW50ZXJ2YWwgb3V0bGllci4NCkEgcG9ydG1hbnRlYXUgdGVzdCByZXR1cm5zIGEgc21hbGxlciBwLXZhbHVlIChhbG1vc3QgY2xvc2UgdG8gWmVybyksIGFsc28gc3VnZ2VzdGluZyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHdoaXRlIG5vaXNlLg0KDQoqKkZpdHRpbmcgdGhlIEFSSU1BIG1vZGVsIHdpdGggdGhlIGV4aXN0aW5nIGRhdGEqKg0KDQpUaGUgcmVzaWR1YWwgZXJyb3JzIHNlZW0gZmluZSB3aXRoIG5lYXIgemVybyBtZWFuIGFuZCB1bmlmb3JtIHZhcmlhbmNlLg0KTGV0J3MgcGxvdCB0aGUgYWN0dWFscyBhZ2FpbnN0IHRoZSBmaXR0ZWQgdmFsdWVzDQoNCmBgYHtyfQ0KI0NvbnZlcnQgdGhlIG1vZGVsIHRvIGRhdGFmcmFtZSBmb3IgcGxvdHRpbmcNCg0KZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzX2RhdGEgPC0gZm9ydGlmeShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIHJlbW92ZV9yb3duYW1lcyAlPiUgDQogIHJlbmFtZSAoZGF0ZSA9IGluZGV4LA0KICAgICAgICAgIHZhY2MgPSBkYXRhKSU+JSANCiAgbXV0YXRlKGluZGV4ID0gc2VxKDE6bnJvdyhkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpKSkNCiAgDQphcmltYV9maXRfcmVzaWQgPC0gdHMoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzKSAtIHJlc2lkKGF1dG9fYXJpbWFfZml0X3ZhY2MpDQoNCmFyaW1hX2ZpdF9kYXRhIDwtIGZvcnRpZnkoYXJpbWFfZml0X3Jlc2lkKSAlPiUgDQogIGNsZWFuX25hbWVzKCkgJT4lIA0KICBtdXRhdGUoZGF0YSA9IHJvdW5kKGRhdGEsMikpDQoNCmZpdF9leGlzdGluZ19kYXRhIDwtIGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllc19kYXRhICU+JSANCiAgaW5uZXJfam9pbihhcmltYV9maXRfZGF0YSwgYnkgPSBjKCJpbmRleCIpKQ0KYGBgDQoNCmBgYHtyfQ0KI3Bsb3R0aW5nIHRoZSBzZXJpZXMgYWxvbmcgd2l0aCB0aGUgZml0dGVkIHZhbHVlcw0KZml0X2V4aXN0aW5nX2RhdGEgJT4lIA0KICBnZ3Bsb3QoKSsNCiAgYWVzKHg9ZGF0ZSwgeSA9IHZhY2MpKw0KICBnZW9tX2xpbmUoY29sb3IgPSIjNWFiNGFjIikrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpLCBjb2xvdXIgPSAicmVkIiApKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIk51bWJlciBvZiBQZW9wbGUgdmFjY2luYXRlZCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiRml0dGluZyB0aGUgQVJJTUEgbW9kZWwgd2l0aCBleGlzdGluZyBkYXRhIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIk0iLCBzY2FsZSA9IDFlLTYpKSsNCiAgY29sb3JfdGhlbWUoKQ0KYGBgDQoNCiMjIFN0ZXAgNiBGb3JlY2FzdCB1c2luZyB0aGUgbW9kZWwNCg0KKipEYXRhIFByZXBhcmF0aW9uIDoqKg0KDQpgYGB7cn0NCiNDb252ZXJ0IHRoZSBtb2RlbCB0byBkYXRhZnJhbWUgZm9yIHBsb3R0aW5nDQpmb3JlY2FzdF9tb2RlbCA8LSBmb3JlY2FzdChhdXRvX2FyaW1hX2ZpdF92YWNjLGxldmVsID0gYyg4MCwgOTUpLCBoID0gNjApIA0KDQpmb3JlY2FzdF9tb2RlbF9kYXRhIDwtIGZvcnRpZnkoZm9yZWNhc3RfbW9kZWwpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIG11dGF0ZShkYXRhID0gcm91bmQoZGF0YSwyKSwNCiAgICAgICAgIGZpdHRlZD0gcm91bmQoZml0dGVkLDIpKSANCg0KZm9yZWNhc3Rfc3RhcnRfZGF0ZSA8LSBhcy5EYXRlKG1heChkYWlseV92YWNjX2hiX3RpbWVzZXJpZXNfZGF0YSRkYXRlKSsxKQ0KZm9yZWNhc3RfZW5kX2RhdGUgPC0gYXMuRGF0ZShmb3JlY2FzdF9zdGFydF9kYXRlKzU5KQ0KDQpmb3JlY2FzdF9kYXRhIDwtIGZvcmVjYXN0X21vZGVsX2RhdGEgJT4lIA0KICBmaWx0ZXIoIShpcy5uYShwb2ludF9mb3JlY2FzdCkpKSAlPiUgDQogIG11dGF0ZShkYXRlID0gc2VxKGZvcmVjYXN0X3N0YXJ0X2RhdGUsZm9yZWNhc3RfZW5kX2RhdGUsIGJ5ID0xKSkgJT4lIA0Kc2VsZWN0KC1kYXRhLC1maXR0ZWQsIC1pbmRleCkgIA0KDQpmaXR0ZWRfZGF0YSA8LSBmb3JlY2FzdF9tb2RlbF9kYXRhICU+JSANCiAgZmlsdGVyKCEoaXMubmEoZGF0YSkpKSAlPiUgDQogIGlubmVyX2pvaW4oZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzX2RhdGEsIGJ5ID0gYygiaW5kZXgiKSkgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JSANCnNlbGVjdChkYXRlLCBkYXRhLCBmaXR0ZWQpIA0KDQpgYGANCg0KKipQbG90dGluZyB0aGUgVmFjY2luYXRpb24gc2VyaWVzIHBsdXMgdGhlIGZvcmVjYXN0IGFuZCA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMqKg0KDQpgYGB7cn0NCmFubm90YXRpb24gPC0gZGF0YS5mcmFtZSgNCiAgIHggPSBjKGFzLkRhdGUoIjAzLTA0LTIwMjEiLCIlZC0lbS0lWSIpLGFzLkRhdGUoIjMxLTEwLTIwMjEiLCIlZC0lbS0lWSIpKSwNCiAgIHkgPSBjKDEwMDAwMDAsMzAwMDAwMCksDQogICBsYWJlbCA9IGMoIlBBU1QiLCAiRlVUVVJFIikNCikNCg0KI1RpbWUgc2VyaWVzIHBsb3RzIGZvciB0aGUgbmV4dCA2MCBkYXlzIGFjY29yZGluZyB0byBiZXN0IEFSSU1BIG1vZGVscyB3aXRoIDgwJeKAkzk1JSBDSS4NCmZpdHRlZF9kYXRhICU+JSANCiAgZ2dwbG90KCkrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID0gZml0dGVkKSwgY29sb3VyID0gInJlZCIgKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID1wb2ludF9mb3JlY2FzdCksIGRhdGEgPSBmb3JlY2FzdF9kYXRhICkrDQogIGdlb21fcmliYm9uKGFlcyh4ID0gZGF0ZSwgeSA9IHBvaW50X2ZvcmVjYXN0LCB5bWluID0gbG9fODAsIHltYXggPSBoaV84MCksIA0KICAgICAgICAgICAgICBkYXRhID0gZm9yZWNhc3RfZGF0YSwgYWxwaGEgPSAwLjMsIGZpbGwgPSAiZ3JlZW4iKSsNCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkYXRlLCB5ID0gcG9pbnRfZm9yZWNhc3QsIHltaW4gPSBsb185NSwgeW1heCA9IGhpXzk1KSwgDQogICAgICAgICAgICAgIGRhdGEgPSBmb3JlY2FzdF9kYXRhLCBhbHBoYSA9IDAuMSkrDQogIGdndGl0bGUoIkZvcmVjYXN0IikgKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIk51bWJlciBvZiBQZW9wbGUgdmFjY2luYXRlZCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgY29sb3JfdGhlbWUoKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJNIiwgc2NhbGUgPSAxZS02KSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogICBnZW9tX3RleHQoZGF0YT1hbm5vdGF0aW9uLCANCiAgICAgICAgICAgICBhZXMoIHg9eCwgeT15LCBsYWJlbD1sYWJlbCksICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICBjb2xvcj0icmVkIiwgDQogICAgICAgICAgICBzaXplPTQgKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID1hcy5EYXRlKCIwOC0xMC0yMDIxIiwiJWQtJW0tJVkiKSwgbGluZXR5cGUgPSAyKQ0KYGBgDQoNCiJGb3JlY2FzdCBvbiBIb3NwaXRhbGlzYXRpb24gKEFSSU1BIE1vZGVsbGluZykiDQoNCioqRGF0YSBQcmVwYXJhdGlvbioqDQoNCmBgYHtyfQ0KI0ZvciBmb3JlY2FzdGluZywgd2UgY2hvc2UgdGhlIGxhdGVzdCBkYXRhDQp0cmVuZF9ob3NwX2hiIDwtIHRyZW5kX2hiX2RhaWx5ICU+JSANCiAgZmlsdGVyIChoYl9uYW1lID09ICJTY290bGFuZCIpICU+JSANCiAgZmlsdGVyKGRhdGUgPj0iMjAyMS0wNi0wMSIpICU+JSANCiAgZmlsdGVyKCEoaXMubmEoaG9zcGl0YWxfYWRtaXNzaW9ucykpKSAlPiUgDQogIHNlbGVjdChkYXRlLCBob3NwaXRhbF9hZG1pc3Npb25zKQ0KDQojIENvbnZlcnQgaXQgaW50byBhIHRpbWUgc2VyaWVzDQpkYWlseV9ob3NwX2hiX3pvbyA8LSB6b28odHJlbmRfaG9zcF9oYiRob3NwaXRhbF9hZG1pc3Npb25zLCANCiAgICAgICAgICAgb3JkZXIuYnk9YXMuRGF0ZSh0cmVuZF9ob3NwX2hiJGRhdGUsIGZvcm1hdD0nJW0vJWQvJVknKSkNCg0KIyBDb252ZXJ0IGl0IGludG8gYSB0aW1lIHNlcmllcw0KZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzIDwtICB0aW1lU2VyaWVzOjphcy50aW1lU2VyaWVzKGRhaWx5X2hvc3BfaGJfem9vKQ0KYGBgDQoNCiMjIFN0ZXAgMSA6IFZpc3VhbGl6ZSB0aGUgdGltZSBzZXJpZXMNCg0KYGBge3J9DQpvcmlnaW5hbF9zZXJpZXM8LWF1dG9wbG90KGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcywgdHMuY29sb3VyID0gJyM1YWI0YWMnKSsNCiAgeGxhYigiTW9udGgiKSArIA0KICB5bGFiKCJOdW1iZXIgb2YgUGVvcGxlIGhvc3BpdGFsaXNlZCIpKw0KICAjc2NhbGVfeF9kYXRlKGJyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIgLSAleSIgKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogIGdndGl0bGUoIlRyZW5kIG9uIEhvc3BpdGFsaXNhdGlvbiIpICsNCiAgY29sb3JfdGhlbWUoKQ0KDQpnZ3Bsb3RseShvcmlnaW5hbF9zZXJpZXMpDQpgYGANCg0KIyMgU3RlcCAyIDogSWRlbnRpZmljYXRpb24gb2YgbW9kZWwgOiAoRmluZGluZyBkOikNCg0KSWRlbnRpZnkgd2hldGhlciB0aGUgdGltZSBzZXJpZXMgaXMgc3RhdGlvbmFyeSAvIG5vbiBzdGF0aW9uYXJ5IHdlIGNhbiB1c2UgQURGIEF1Z21lbnRlZCBEaWNrZXktRnVsbGVyIHRlc3QNCg0KYGBge3J9DQphZGZfdGVzdF9ob3NwIDwtIGFkZi50ZXN0KGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcykNCmFkZl90ZXN0X2hvc3ANCmBgYA0KDQpUaGUgdGltZSBzZXJpZXMgaXMgbm90IHN0YXRpb25hcnkgc2luY2Ugd2UgaGF2ZSBhIGhpZ2ggcC12YWx1ZSAocC12YWx1ZSBtdXN0IGJlIFw8IDAuMDUpLg0KU28gd2UgYXBwbHkgZGlmZmVyZW5jZQ0KDQpgYGB7cn0NCmZpcnN0X2RpZmZfaG9zcDwtIGRpZmYoZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzKQ0KYWRmX3Rlc3QxX2hvc3AgPC0gYWRmLnRlc3QobmEub21pdChmaXJzdF9kaWZmX2hvc3ApKQ0KYWRmX3Rlc3QxX2hvc3ANCmBgYA0KDQpDcmVhdGUgYSBkYXRhZnJhbWUgdG8gY29tcGFyZQ0KDQpgYGB7cn0NCmFkZl9kYXRhX2hvc3AgPC0gZGF0YS5mcmFtZShEYXRhID0gYygiT3JpZ2luYWwiLCAiRmlyc3QtT3JkZXJlZCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBEaWNrZXlfRnVsbGVyID0gYyhhZGZfdGVzdF9ob3NwJHN0YXRpc3RpYywgYWRmX3Rlc3QxX2hvc3Akc3RhdGlzdGljKSwNCiAgICAgICAgICAgICAgICAgICAgICAgcF92YWx1ZSA9IGMoYWRmX3Rlc3RfaG9zcCRwLnZhbHVlLGFkZl90ZXN0MV9ob3NwJHAudmFsdWUpKQ0KYWRmX2RhdGFfaG9zcA0KYGBgDQoNCkluaXRpYWxseSB0aGUgcC12YWx1ZSBpcyBoaWdoIHdoaWNoIGluZGljYXRlcyB0aGF0IHRoZSBUaW1lIFNlcmllcyBpcyBub3Qgc3RhdGlvbmFyeS4NClNvIHdlIGFwcGx5IGRpZmZlcmVuY2UgMSB0aW1lLg0KQWZ0ZXIgdGhlIGZpcnN0IGRpZmZlcmVuY2UsIHRoZSBwLXZhbHVlIFw8IHNpZ25pZmljYW5jZSBsZXZlbCAoMC4wNSkgU28gd2UgY2FuIGNvbmNsdWRlIHRoYXQgdGhlIGRpZmZlcmVuY2UgZGF0YSBhcmUgc3RhdGlvbmFyeS4NClNvIGRpZmZlcmVuY2UgKGQgPSAxKQ0KDQpPdGhlciBtZXRob2Q6DQoNCmBgYHtyfQ0KbmRpZmZzKGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcykNCmBgYA0KDQpMZXQncyBwbG90IHRoZSBGaXJzdCBPcmRlciBEaWZmZXJlbmNlIFNlcmllcw0KDQpPcmRlciBvZiBmaXJzdCBkaWZmZXJlbmNlDQoNCmBgYHtyfQ0KcDwtIGF1dG9wbG90KGZpcnN0X2RpZmZfaG9zcCwgdHMuY29sb3VyID0gJyM1YWI0YWMnKSArDQogIHhsYWIoIk1vbnRoIikgKyANCiAgeWxhYigiSE9TUElUQUxJWkFUSU9OIikrDQogIyBzY2FsZV94X2RhdGUoYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICIlYiAtICV5IiApKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiRmlyc3QtT3JkZXIgRGlmZmVyZW5jZSBTZXJpZXMiKSArDQogIGNvbG9yX3RoZW1lKCkNCg0KZ2dwbG90bHkocCkNCmBgYA0KDQojIyBTdGVwIDM6IEVzdGltYXRlIHRoZSBwYXJhbWV0ZXJzIChGaW5kaW5nIHAgYW5kIHEpDQoNCkZvciBvdXIgbW9kZWwgQVJJTUEgKHAsZCxxKSwgd2UgZm91bmQgZCA9IDEsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gZ2V0IHRoZSB2YWx1ZXMgb2YgcCBhbmQgcSwgdGhlIG9yZGVyIG9mIEFSIGFuZCBNQSBwYXJ0Lg0KUGxvdCBBQ0YgYW5kIFBBQ0YgY2hhcnRzIHRvIGlkZW50aWZ5IHEgYW5kIHAgcmVzcGVjdGl2ZWx5Lg0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDIsMikpDQphY2ZfaG9zcCAgPC0gYWNmMShkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXMsIGNvbD0yOjcsIGx3ZD00KQ0KcGFjZl9ob3NwIDwtIGFjZjEoZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzLCAgcGFjZiA9IFRSVUUsIGNvbD0yOjcsIGx3ZD00KQ0KYWNmX2hvc3AgIDwtIGFjZjEoZmlyc3RfZGlmZl9ob3NwLCBjb2w9Mjo3LCBsd2Q9NCkNCnBhY2ZfaG9zcCA8LSBhY2YxKGZpcnN0X2RpZmZfaG9zcCwgIHBhY2YgPSBUUlVFLCBjb2w9Mjo3LCBsd2Q9NCkNCmBgYA0KDQpUaGUgQUNGIGFuZCBQQUNGIHBsb3RzIG9mIHRoZSBkaWZmZXJlbmNlZCBkYXRhIHNob3cgdGhlIGZvbGxvd2luZyBwYXR0ZXJuczoNCg0KVGhlIEFDRiBpcyBzaW51c29pZGFsIGFuZCB0aGVyZSBpcyBhIHNpZ25pZmljYW50IHNwaWtlIGF0IGxhZyAzIGluIHRoZSBQQUNGIFNvIHRoZSBkYXRhIG1heSBmb2xsb3cgYW4gQVIoMykgbW9kZWwNCg0KVGhlIFBBQ0YgaXMgc2ludXNvaWRhbCBhbmQgdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgMiBpbiB0aGUgQUNGIFNvIHRoZSBkYXRhIG1heSBmb2xsb3cgYW4gTUEoMikgbW9kZWwNCg0KU28gd2UgcHJvcG9zZSB0aHJlZSBBUk1BIG1vZGVscyBmb3IgdGhlIGRpZmZlcmVuY2VkIGRhdGE6IEFSTUEocCxxKSBBUk1BKDMsMiksIEFSTUEoMywwKSBhbmQgQVJNQSgwLDIpLg0KDQpUaGF0IGlzLCBmb3IgdGhlIG9yaWdpbmFsIHRpbWUgc2VyaWVzLCB3ZSBwcm9wb3NlIHRocmVlIEFSSU1BIG1vZGVscyxBUklNQShwLGQscSkgQVJJTUEoMywxLDIpLCBBUklNQSgzLDEsMCkgYW5kIEFSTUEoMCwxLDIpLg0KDQojIyBTdGVwIDQ6IEJ1aWxkIHRoZSBBUklNQSBtb2RlbA0KDQojIyMgKipNYW51YWwgQVJJTUE6KioNCg0KYGBge3J9DQphcmltYV9maXRfaG9zcF8xID0gQXJpbWEoZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDIpKQ0KYXJpbWFfZml0X2hvc3BfMiA9IEFyaW1hKGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcywgb3JkZXIgPSBjKDMsMSwwKSkNCmFyaW1hX2ZpdF9ob3NwXzMgPSBBcmltYShkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXMsIG9yZGVyID0gYygwLDEsMikpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGFyaW1hX2ZpdF9ob3NwXzEpDQpzdW1tYXJ5KGFyaW1hX2ZpdF9ob3NwXzIpDQpzdW1tYXJ5KGFyaW1hX2ZpdF9ob3NwXzMpDQpgYGANCg0KYGBge3J9DQoNCnRleHJlZzo6c2NyZWVucmVnKGxpc3QoYXJpbWFfZml0X2hvc3BfMSwgYXJpbWFfZml0X2hvc3BfMiwgYXJpbWFfZml0X2hvc3BfMyksDQogICAgICAgICAgICAgICAgY3VzdG9tLm1vZGVsLm5hbWVzID1jKCJBUklNQSgzLDEsMikiLCJBUklNQSgzLDEsMCkiLCJBUklNQSgwLDEsNCkiKSwNCiAgICAgICAgICAgICAgICBjZW50ZXIgPSBUUlVFLA0KICAgICAgICAgICAgICAgIHRhYmxlID0gRkFMU0UpDQpgYGANCg0KYGBge3J9DQojRnVuY3Rpb24gZm9yIEF1dG9tYXRlZCBBUklNQQ0KDQoNCmF1dG9fYXJpbWFfZml0X2hvc3AgPC0gYXV0by5hcmltYShsYWcoZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzLGsgPWxhZ192YWx1ZSApLA0KICAgICAgICAgICAgICAgICAgc2Vhc29uYWw9RkFMU0UsDQogICAgICAgICAgICAgICAgICBzdGVwd2lzZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIGFwcHJveGltYXRpb249RkFMU0UsDQogICAgICAgICAgICAgICAgICB0cmFjZSA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQojIyMgQXV0b21hdGVkIEFSSU1BDQoNCmBgYHtyfQ0KI0xhZyBpcyB1c2VkIHRvIGJlc3QgZml0IHRoZSBtb2RlbA0KYXV0b19hcmltYV9maXRfaG9zcCA8LSBhdXRvLmFyaW1hKGxhZyhkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXMpLA0KICAgICAgICAgICAgICAgICAgc2Vhc29uYWw9RkFMU0UsDQogICAgICAgICAgICAgICAgICBzdGVwd2lzZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIGFwcHJveGltYXRpb249RkFMU0UsDQogICAgICAgICAgICAgICAgICB0cmFjZSA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICkNCmF1dG9fYXJpbWFfZml0X2hvc3ANCmBgYA0KDQpBdXRvbWF0ZWQgQVJJTUEgY29uZmlybXMgdGhhdCB0aGUgQVJJTUEoMywxLDIpIHNlZW1zIGdvb2QgYmFzZWQgb24gQUlDDQoNCmBgYHtyfQ0KY29lZjwtbG10ZXN0Ojpjb2VmdGVzdChhdXRvX2FyaW1hX2ZpdF9ob3NwKQ0KY29lZg0KYGBgDQoNCkFsbCBjb2VmZmljaWVudHMgYXJlIHNpZ25pZmljYW50IGV4Y2VwdCBhcjMuDQoNCiMjIyBNb2RlbCBTZWxlY3Rpb24gQ3JpdGVyaWEgOg0KDQpBUklNQSBtb2RlbHMgd2l0aCBtaW5pbXVtIEFJQywgUk1TRSBhbmQgTUFQRSBjcml0ZXJpYSB3ZXJlIGNob3NlbiBhcyB0aGUgYmVzdCBtb2RlbHMuDQpCYXNlZCBvbiBBa2Fpa2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uIChBSUMpIGFib3ZlLCBhbiBBUklNQSgzLCAxLCAyKSBtb2RlbCBzZWVtcyBiZXN0Lg0KDQojIyBTdGVwIDU6IENoZWNrIGZvciBEaWFnbm9zdGljcw0KDQpMZXQncyBwbG90IHRoZSBkaWFnbm9zdGljcyB3aXRoIHRoZSByZXN1bHRzIHRvIG1ha2Ugc3VyZSB0aGUgbm9ybWFsaXR5IGFuZCBjb3JyZWxhdGlvbiBhc3N1bXB0aW9ucyBmb3IgdGhlIG1vZGVsIGhvbGQuDQpJZiB0aGUgcmVzaWR1YWxzIGxvb2sgbGlrZSB3aGl0ZSBub2lzZSwgcHJvY2VlZCB3aXRoIGZvcmVjYXN0IGFuZCBwcmVkaWN0aW9uLCBvdGhlcndpc2UgcmVwZWF0IHRoZSBtb2RlbCBidWlsZGluZy4NCg0KYGBge3J9DQpyZXMgPC1jaGVja3Jlc2lkdWFscyhhdXRvX2FyaW1hX2ZpdF9ob3NwLCB0aGVtZSA9IGNvbG9yX3RoZW1lKCkpDQpyZXMNCmBgYA0KDQpUaGUgQUNGIHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSBBUklNQSgzLDEsMikgbW9kZWwgc2hvd3MgdGhhdCBhbGwgYXV0byBjb3JyZWxhdGlvbnMgYXJlIGFsbW9zdCB3aXRoaW4gdGhlIHRocmVzaG9sZCBsaW1pdHMsIHdpdGggcmVzaWR1YWxzLg0KQSBwb3J0bWFudGVhdSB0ZXN0IChManVuZy1Cb3ggdGVzdCkgcmV0dXJucyBhIHNtYWxsZXIgcC12YWx1ZSAsIGFsc28gc3VnZ2VzdGluZyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHdoaXRlIG5vaXNlLg0KDQpGaXR0aW5nIHRoZSBBUklNQSBtb2RlbCB3aXRoIHRoZSBleGlzdGluZyBkYXRhDQoNClRoZSByZXNpZHVhbCBlcnJvcnMgc2VlbSBmaW5lIHdpdGggbmVhciB6ZXJvIG1lYW4gYW5kIHVuaWZvcm0gdmFyaWFuY2UuDQpMZXQncyBwbG90IHRoZSBhY3R1YWxzIGFnYWluc3QgdGhlIGZpdHRlZCB2YWx1ZXMNCg0KKipDb252ZXJ0IG1vZGVsIGFuZCB0aW1lIHNlcmllcyB0byBkYXRhZnJhbWUgZm9yIHBsb3R0aW5nKioNCg0KYGBge3J9DQpkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXNfZGF0YSA8LSBmb3J0aWZ5KGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcykgJT4lIA0KICBjbGVhbl9uYW1lcygpICU+JSANCiAgcmVtb3ZlX3Jvd25hbWVzICU+JSANCiAgcmVuYW1lIChkYXRlID0gaW5kZXgsDQogICAgICAgICAgaG9zcCA9IGRhdGEpJT4lIA0KICBtdXRhdGUoaW5kZXggPSBzZXEoMTpucm93KGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcykpKQ0KICANCmFyaW1hX2ZpdF9yZXNpZCA8LSB0cyhkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXNbMTpucm93KGRhaWx5X2hvc3BfaGJfdGltZXNlcmllcyldKSAtIHJlc2lkKGF1dG9fYXJpbWFfZml0X2hvc3ApDQoNCmFyaW1hX2ZpdF9kYXRhIDwtIGZvcnRpZnkoYXJpbWFfZml0X3Jlc2lkKSAlPiUgDQogIGNsZWFuX25hbWVzKCkgJT4lIA0KICBtdXRhdGUoZGF0YSA9IHJvdW5kKGRhdGEsMikpDQoNCmZpdF9leGlzdGluZ19kYXRhIDwtIGRhaWx5X2hvc3BfaGJfdGltZXNlcmllc19kYXRhICU+JSANCiAgaW5uZXJfam9pbihhcmltYV9maXRfZGF0YSwgYnkgPSBjKCJpbmRleCIpKQ0KYGBgDQoNCioqUGxvdHRpbmcgdGhlIHNlcmllcyBhbG9uZyB3aXRoIHRoZSBmaXR0ZWQgdmFsdWVzKioNCg0KYGBge3J9DQpmaXRfZXhpc3RpbmdfaG9zcF9wbG90IDwtIGZpdF9leGlzdGluZ19kYXRhICU+JSANCiAgbXV0YXRlIChkYXRlID0gYXMuRGF0ZShkYXRlKSkgJT4lIA0KICBnZ3Bsb3QoKSsNCiAgYWVzKHg9ZGF0ZSwgeSA9IGhvc3ApKw0KICBnZW9tX2xpbmUoY29sb3IgPSIjNWFiNGFjIikrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpLCBjb2xvdXIgPSAicmVkIiApKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIlBhdGllbnQgSG9zcGl0YWxpc2VkIikrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJGaXR0aW5nIHRoZSBBUklNQSBtb2RlbCB3aXRoIGV4aXN0aW5nIGRhdGEiKSArDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogICNzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIk0iLCBzY2FsZSA9IDFlLTYpKSsNCiAgY29sb3JfdGhlbWUoKQ0KDQpnZ3Bsb3RseShmaXRfZXhpc3RpbmdfaG9zcF9wbG90KQ0KYGBgDQoNCiMjIFN0ZXAgNiBGb3JlY2FzdCB1c2luZyB0aGUgbW9kZWwNCg0KKipEYXRhIFByZXBhcmF0aW9uIDoqKg0KDQpgYGB7cn0NCmZvcmVjYXN0X21vZGVsIDwtIGZvcmVjYXN0KGF1dG9fYXJpbWFfZml0X2hvc3AsbGV2ZWwgPSBjKDgwLCA5NSksIGggPSAzMCkgDQoNCiNDb252ZXJ0IHRoZSBtb2RlbCB0byBkYXRhZnJhbWUgZm9yIHBsb3R0aW5nDQoNCmZvcmVjYXN0X21vZGVsX2RhdGEgPC0gZm9ydGlmeShmb3JlY2FzdF9tb2RlbCkgJT4lIA0KICBjbGVhbl9uYW1lcygpICU+JSANCiAgbXV0YXRlKGRhdGEgPSByb3VuZChkYXRhLDIpLA0KICAgICAgICAgZml0dGVkPSByb3VuZChmaXR0ZWQsMikpICU+JSANCiAgbXV0YXRlIChsb184MCA9IGlmZWxzZShsb184MCA8IDAsMCxsb184MCksDQogICAgICAgICAgbG9fOTUgPSBpZmVsc2UobG9fOTUgPCAwLDAsbG9fOTUpDQogICkNCg0KZm9yZWNhc3Rfc3RhcnRfZGF0ZSA8LSBhcy5EYXRlKG1heChkYWlseV9ob3NwX2hiX3RpbWVzZXJpZXNfZGF0YSRkYXRlKSsxKQ0KZm9yZWNhc3RfZW5kX2RhdGUgPC0gYXMuRGF0ZShmb3JlY2FzdF9zdGFydF9kYXRlKzI5KQ0KDQpmb3JlY2FzdF9kYXRhIDwtIGZvcmVjYXN0X21vZGVsX2RhdGEgJT4lIA0KICBmaWx0ZXIoIShpcy5uYShwb2ludF9mb3JlY2FzdCkpKSAlPiUgDQogIG11dGF0ZShkYXRlID0gc2VxKGZvcmVjYXN0X3N0YXJ0X2RhdGUsZm9yZWNhc3RfZW5kX2RhdGUsIGJ5ID0xKSkgJT4lIA0Kc2VsZWN0KC1kYXRhLC1maXR0ZWQsIC1pbmRleCkgIA0KDQpmaXR0ZWRfZGF0YSA8LSBmb3JlY2FzdF9tb2RlbF9kYXRhICU+JSANCiAgZmlsdGVyKCEoaXMubmEoZGF0YSkpKSAlPiUgDQogIGlubmVyX2pvaW4oZGFpbHlfaG9zcF9oYl90aW1lc2VyaWVzX2RhdGEsIGJ5ID0gYygiaW5kZXgiKSkgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JSANCnNlbGVjdChkYXRlLCBkYXRhLCBmaXR0ZWQpIA0KDQpgYGANCg0KKipQbG90dGluZyB0aGUgVmFjY2luYXRpb24gc2VyaWVzIHBsdXMgdGhlIGZvcmVjYXN0IGFuZCA4MCAtIDk1JSBwcmVkaWN0aW9uIGludGVydmFscyoqDQoNCmBgYHtyfQ0KDQphbm5vdGF0aW9uIDwtIGRhdGEuZnJhbWUoDQogICB4ID0gYyhhcy5EYXRlKCIxMy0wOC0yMDIxIiwiJWQtJW0tJVkiKSxhcy5EYXRlKCIzMS0xMC0yMDIxIiwiJWQtJW0tJVkiKSksDQogICB5ID0gYygxODAsMjAwKSwNCiAgIGxhYmVsID0gYygiUEFTVCIsICJGVVRVUkUiKQ0KKQ0KDQojVGltZSBzZXJpZXMgcGxvdHMgZm9yIHRoZSBuZXh0IDYwIGRheXMgYWNjb3JkaW5nIHRvIGJlc3QgQVJJTUEgbW9kZWxzIHdpdGggODAl4oCTOTUlIENJLg0KZm9yZWNhc3RfZGF0YV9ob3NwX3Bsb3QgPC1maXR0ZWRfZGF0YSAlPiUgDQogIGdncGxvdCgpKw0KICBnZW9tX2xpbmUoYWVzKHg9IGRhdGUsIHkgPSBkYXRhKSwgY29sb3IgPSAiIzVhYjRhYyIpKw0KICAjZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID0gZml0dGVkKSwgY29sb3VyID0gInJlZCIgKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID1wb2ludF9mb3JlY2FzdCksIGNvbG9yID0iYmx1ZSIsIHNpemUgPSAwLjUsDQogICAgICAgICAgICAgZGF0YSA9IGZvcmVjYXN0X2RhdGEgKSsNCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkYXRlLCB5ID0gcG9pbnRfZm9yZWNhc3QsIHltaW4gPSBsb184MCwgeW1heCA9IGhpXzgwKSwgDQogICAgICAgICAgICAgIGRhdGEgPSBmb3JlY2FzdF9kYXRhLCBhbHBoYSA9IDAuMywgZmlsbCA9ICJncmVlbiIpKw0KICBnZW9tX3JpYmJvbihhZXMoeCA9IGRhdGUsIHkgPSBwb2ludF9mb3JlY2FzdCwgeW1pbiA9IGxvXzk1LCB5bWF4ID0gaGlfOTUpLCANCiAgICAgICAgICAgICAgZGF0YSA9IGZvcmVjYXN0X2RhdGEsIGFscGhhID0gMC4xKSsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1hcy5udW1lcmljKG1heChkYXRlKSkpLGNvbG9yPSIjZjFhMzQwIiwgbGluZXR5cGU9ImRhc2hlZCIsZGF0YSA9IGZpdHRlZF9kYXRhKSsNCiAgZ2d0aXRsZSgiUHJvamVjdGlvbiBvZiBIb3NwaXRhbGlzYXRpb24iKSArDQogIHhsYWIoIk1vbnRoIikgKyANCiAgeWxhYigiUGF0aWVudCBIb3NwaXRhbGlzZWQiKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogICBnZW9tX3RleHQoZGF0YT1hbm5vdGF0aW9uLCANCiAgICAgICAgICAgICBhZXMoIHg9eCwgeT15LCBsYWJlbD1sYWJlbCksICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICBjb2xvcj0iYmx1ZSIsIA0KICAgICAgICAgICAgc2l6ZT00ICkNCiAgDQogIGdncGxvdGx5KGZvcmVjYXN0X2RhdGFfaG9zcF9wbG90KQ0KICANCmBgYA0KDQoiRm9yZWNhc3Qgb24gRGVhdGhzIChBUklNQSBNb2RlbGxpbmcpIg0KDQoqKkRhdGEgUHJlcGFyYXRpb24qKg0KDQpgYGB7cn0NCiNGb3IgZm9yZWNhc3RpbmcsIHdlIGNob3NlIHRoZSBsYXRlc3QgZGF0YQ0KdHJlbmRfZGVhdGhfaGIgPC0gdHJlbmRfaGJfZGFpbHkgJT4lIA0KICBmaWx0ZXIgKGhiX25hbWUgPT0gIlNjb3RsYW5kIikgJT4lIA0KICBmaWx0ZXIoZGF0ZSA+PSIyMDIxLTA2LTAxIikgJT4lIA0KICBmaWx0ZXIoIShpcy5uYShkYWlseV9kZWF0aHMpKSkgJT4lIA0KICBzZWxlY3QoZGF0ZSwgZGFpbHlfZGVhdGhzKQ0KDQojIENvbnZlcnQgaXQgaW50byBhIHRpbWUgc2VyaWVzDQpkYWlseV9kZWF0aF9oYl96b28gPC0gem9vKHRyZW5kX2RlYXRoX2hiJGRhaWx5X2RlYXRocywgDQogICAgICAgICAgIG9yZGVyLmJ5PWFzLkRhdGUodHJlbmRfZGVhdGhfaGIkZGF0ZSwgZm9ybWF0PSclbS8lZC8lWScpKQ0KDQojIENvbnZlcnQgaXQgaW50byBhIHRpbWUgc2VyaWVzDQpkYWlseV9kZWF0aF9oYl90aW1lc2VyaWVzIDwtICB0aW1lU2VyaWVzOjphcy50aW1lU2VyaWVzKGRhaWx5X2RlYXRoX2hiX3pvbykNCmBgYA0KDQojIyBTdGVwIDEgOiBWaXN1YWxpemUgdGhlIHRpbWUgc2VyaWVzDQoNCmBgYHtyfQ0Kb3JpZ2luYWxfc2VyaWVzX2RlYXRoPC1hdXRvcGxvdChkYWlseV9kZWF0aF9oYl90aW1lc2VyaWVzLCB0cy5jb2xvdXIgPSAnIzVhYjRhYycpKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIlBhdGllbnQgZGllZCIpKw0KICAjc2NhbGVfeF9kYXRlKGJyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIgLSAleSIgKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogIGdndGl0bGUoIlRyZW5kIG9uIERlYXRocyIpICsNCiAgY29sb3JfdGhlbWUoKQ0KDQpnZ3Bsb3RseShvcmlnaW5hbF9zZXJpZXNfZGVhdGgpDQpgYGANCg0KIyMgU3RlcCAyIDogSWRlbnRpZmljYXRpb24gb2YgbW9kZWwgOiAoRmluZGluZyBkOikNCg0KSWRlbnRpZnkgd2hldGhlciB0aGUgdGltZSBzZXJpZXMgaXMgc3RhdGlvbmFyeSAvIG5vbiBzdGF0aW9uYXJ5IHdlIGNhbiB1c2UgQURGIEF1Z21lbnRlZCBEaWNrZXktRnVsbGVyIHRlc3QNCg0KYGBge3J9DQphZGZfdGVzdF9kZWF0aCA8LSBhZGYudGVzdChkYWlseV9kZWF0aF9oYl90aW1lc2VyaWVzKQ0KYWRmX3Rlc3RfZGVhdGgNCmBgYA0KDQpUaGUgdGltZSBzZXJpZXMgaXMgbm90IHN0YXRpb25hcnkgc2luY2Ugd2UgaGF2ZSBhIGhpZ2ggcC12YWx1ZSAocC12YWx1ZSBtdXN0IGJlIFw8IDAuMDUpLg0KU28gd2UgYXBwbHkgZGlmZmVyZW5jZQ0KDQpgYGB7cn0NCmZpcnN0X2RpZmZfZGVhdGg8LSBkaWZmKGRhaWx5X2RlYXRoX2hiX3RpbWVzZXJpZXMpDQphZGZfdGVzdDFfZGVhdGggPC0gYWRmLnRlc3QobmEub21pdChmaXJzdF9kaWZmX2RlYXRoKSkNCmFkZl90ZXN0MV9kZWF0aA0KYGBgDQoNCkNyZWF0ZSBhIGRhdGFmcmFtZSB0byBjb21wYXJlDQoNCmBgYHtyfQ0KYWRmX2RhdGFfZGVhdGggPC0gZGF0YS5mcmFtZShEYXRhID0gYygiT3JpZ2luYWwiLCAiRmlyc3QtT3JkZXJlZCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBEaWNrZXlfRnVsbGVyID0gYyhhZGZfdGVzdF9kZWF0aCRzdGF0aXN0aWMsIGFkZl90ZXN0MV9kZWF0aCRzdGF0aXN0aWMpLA0KICAgICAgICAgICAgICAgICAgICAgICBwX3ZhbHVlID0gYyhhZGZfdGVzdF9kZWF0aCRwLnZhbHVlLGFkZl90ZXN0MV9kZWF0aCRwLnZhbHVlKSkNCmFkZl9kYXRhX2RlYXRoDQpgYGANCg0KSW5pdGlhbGx5IHRoZSBwLXZhbHVlIGlzIGhpZ2ggd2hpY2ggaW5kaWNhdGVzIHRoYXQgdGhlIFRpbWUgU2VyaWVzIGlzIG5vdCBzdGF0aW9uYXJ5Lg0KU28gd2UgYXBwbHkgZGlmZmVyZW5jZSAxIHRpbWUuDQpBZnRlciB0aGUgZmlyc3QgZGlmZmVyZW5jZSwgdGhlIHAtdmFsdWUgXDwgc2lnbmlmaWNhbmNlIGxldmVsICgwLjA1KSBTbyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGUgZGlmZmVyZW5jZSBkYXRhIGFyZSBzdGF0aW9uYXJ5Lg0KU28gZGlmZmVyZW5jZSAoZCA9IDEpDQoNCk90aGVyIG1ldGhvZDoNCg0KYGBge3J9DQpuZGlmZnMoZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllcykNCmBgYA0KDQpMZXQncyBwbG90IHRoZSBGaXJzdCBPcmRlciBEaWZmZXJlbmNlIFNlcmllcw0KDQpPcmRlciBvZiBmaXJzdCBkaWZmZXJlbmNlDQoNCmBgYHtyfQ0KDQpmaXJzdF9kaWZmX2RlYXRoPC0gZGlmZihkYWlseV9kZWF0aF9oYl90aW1lc2VyaWVzKQ0KcDwtIGF1dG9wbG90KGZpcnN0X2RpZmZfZGVhdGgsIHRzLmNvbG91ciA9ICcjNWFiNGFjJykgKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIkRFQVRIIikrDQogIyBzY2FsZV94X2RhdGUoYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICIlYiAtICV5IiApKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiRmlyc3QtT3JkZXIgRGlmZmVyZW5jZSBTZXJpZXMiKSArDQogIGNvbG9yX3RoZW1lKCkNCg0KZ2dwbG90bHkocCkNCmBgYA0KDQojIyBTdGVwIDMgRXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgKEZpbmRpbmcgcCBhbmQgcSkNCg0KRm9yIG91ciBtb2RlbCBBUklNQSAocCxkLHEpLCB3ZSBmb3VuZCBkID0gMSwgdGhlIG5leHQgc3RlcCBpcyB0byBnZXQgdGhlIHZhbHVlcyBvZiBwIGFuZCBxLCB0aGUgb3JkZXIgb2YgQVIgYW5kIE1BIHBhcnQuDQpQbG90IEFDRiBhbmQgUEFDRiBjaGFydHMgdG8gaWRlbnRpZnkgcSBhbmQgcCByZXNwZWN0aXZlbHkuDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMiwyKSkNCmFjZl9kZWF0aCAgPC0gYWNmMShmaXJzdF9kaWZmX2RlYXRoLCBjb2w9Mjo3LCBsd2Q9NCkNCnBhY2ZfZGVhdGggPC0gYWNmMShmaXJzdF9kaWZmX2RlYXRoLCAgcGFjZiA9IFRSVUUsIGNvbD0yOjcsIGx3ZD00KQ0KYGBgDQoNClRoZSBBQ0YgYW5kIFBBQ0YgcGxvdHMgdGFwZXJzIHRvIHplbyBhdCBvbmUgcG9pbnQuDQpTbyBpdCBmb2xsb3dzIGFuIEFSTUEgbW9kZWwNCg0KVGhlIFBBQ0Ygbm9uLXplcm8gdmFsdWUgYXQgbGFnIDMgU28gdGhlIGRhdGEgbWF5IGZvbGxvdyBhbiBBUigzKSBtb2RlbA0KDQpUaGUgQUNGIG5vbiAtIHplcm8gdmFsdWUgYXQgbGFnIDIgU28gaXQgY2FuIGJlIE1BICgyKQ0KDQpTbyB3ZSBwcm9wb3NlIHRocmVlIEFSTUEgbW9kZWxzIGZvciB0aGUgZGlmZmVyZW5jZWQgZGF0YTogQVJNQShwLHEpIEFSTUEoMywyKSwgQVJNQSgzLDApIGFuZCBBUk1BKDAsMikuDQoNClRoYXQgaXMsIGZvciB0aGUgb3JpZ2luYWwgdGltZSBzZXJpZXMsIHdlIHByb3Bvc2UgdGhyZWUgQVJJTUEgbW9kZWxzLEFSSU1BKHAsZCxxKSBBUklNQSgzLDEsMiksIEFSSU1BKDMsMSwwKSBhbmQgQVJNQSgwLDEsMikuDQoNCiMjIFN0ZXAgNCBCdWlsZCB0aGUgQVJJTUEgbW9kZWwNCg0KIyMjIE1hbnVhbCBBUklNQQ0KDQpgYGB7cn0NCmFyaW1hX2ZpdF9kdGhfMSA9IEFyaW1hKGRhaWx5X2RlYXRoX2hiX3RpbWVzZXJpZXMsIG9yZGVyID0gYygzLDEsMikpDQphcmltYV9maXRfZHRoXzIgPSBBcmltYShkYWlseV9kZWF0aF9oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDApKQ0KYXJpbWFfZml0X2R0aF8zID0gQXJpbWEoZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllcywgb3JkZXIgPSBjKDAsMSwyKSkNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoYXJpbWFfZml0X2R0aF8xKQ0Kc3VtbWFyeShhcmltYV9maXRfZHRoXzIpDQpzdW1tYXJ5KGFyaW1hX2ZpdF9kdGhfMykNCmBgYA0KDQpBbm90aGVyIHdheSBvZiBjaGVja2luZyBBSUMNCg0KYGBge3J9DQp0ZXhyZWc6OnNjcmVlbnJlZyhsaXN0KGFyaW1hX2ZpdF9kdGhfMSwgYXJpbWFfZml0X2R0aF8yLCBhcmltYV9maXRfZHRoXzMpLA0KICAgICAgICAgICAgICAgIGN1c3RvbS5tb2RlbC5uYW1lcyA9YygiQVJJTUEoMywxLDIpIiwiQVJJTUEoMywxLDApIiwiQVJJTUEoMCwxLDIpIiksDQogICAgICAgICAgICAgICAgY2VudGVyID0gVFJVRSwNCiAgICAgICAgICAgICAgICB0YWJsZSA9IEZBTFNFKQ0KYGBgDQoNCkJhc2VkIG9uIHRoaXMsIEFSSU1BIG1vZGVsICgzLDEsMikgc2VlbXMgYmVzdC4NCldlIGNhbiB2ZXJpZnkgdGhlIHNhbWUgdXNpbmcgYXV0b21hdGVkIG1ldGhvZA0KDQojIyMgQXV0b21hdGVkIEFSSU1BDQoNCmBgYHtyfQ0KYXV0b19hcmltYV9maXRfZGVhdGggPC0gYXV0by5hcmltYShsYWcoZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllcyksDQogICAgICAgICAgICAgICAgICBzZWFzb25hbD1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIHN0ZXB3aXNlPUZBTFNFLA0KICAgICAgICAgICAgICAgICAgYXBwcm94aW1hdGlvbj1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIHRyYWNlID0gVFJVRQ0KICAgICAgICAgICAgICAgICAgKQ0KYXV0b19hcmltYV9maXRfZGVhdGgNCmBgYA0KDQpIb3dldmVyIEF1dG9tYXRlZCBBUklNQSBhbHNvIGNvbmZpcm1zIHRoYXQgdGhlIEFSSU1BKDIsMSwzKSBzZWVtcyBnb29kIGJhc2VkIG9uIEFJQw0KDQpgYGB7cn0NCmNvZWZfZHRoPC1sbXRlc3Q6OmNvZWZ0ZXN0KGF1dG9fYXJpbWFfZml0X2RlYXRoKQ0KY29lZl9kdGgNCmBgYA0KDQoqKk1vZGVsIFNlbGVjdGlvbiBDcml0ZXJpYSA6KioNCg0KQVJJTUEgbW9kZWxzIHdpdGggbWluaW11bSBBSUMsIFJNU0UgYW5kIE1BUEUgY3JpdGVyaWEgd2VyZSBjaG9zZW4gYXMgdGhlIGJlc3QgbW9kZWxzLg0KQmFzZWQgb24gQWthaWtlIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQUlDKSBhYm92ZSwgYW4gQVJJTUEoMiwgMSwgMykgbW9kZWwgc2VlbXMgYmVzdC4NCg0KIyMgU3RlcCA1IENoZWNrIGZvciBEaWFnbm9zdGljcw0KDQpMZXQncyBwbG90IHRoZSBkaWFnbm9zdGljcyB3aXRoIHRoZSByZXN1bHRzIHRvIG1ha2Ugc3VyZSB0aGUgbm9ybWFsaXR5IGFuZCBjb3JyZWxhdGlvbiBhc3N1bXB0aW9ucyBmb3IgdGhlIG1vZGVsIGhvbGQuDQpJZiB0aGUgcmVzaWR1YWxzIGxvb2sgbGlrZSB3aGl0ZSBub2lzZSwgcHJvY2VlZCB3aXRoIGZvcmVjYXN0IGFuZCBwcmVkaWN0aW9uLCBvdGhlcndpc2UgcmVwZWF0IHRoZSBtb2RlbCBidWlsZGluZy4NCg0KYGBge3J9DQpyZXNfZHRoIDwtY2hlY2tyZXNpZHVhbHMoYXV0b19hcmltYV9maXRfZGVhdGgsIHRoZW1lID0gY29sb3JfdGhlbWUoKSkNCnJlc19kdGgNCmBgYA0KDQpUaGUgQUNGIHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSBBUklNQSgyLDEsMykgbW9kZWwgc2hvd3MgdGhhdCBhbGwgYXV0byBjb3JyZWxhdGlvbnMgYXJlIHdpdGhpbiB0aGUgdGhyZXNob2xkIGxpbWl0cyBleGNlcHQgMSwgQnV0IHBvcnRtYW50ZWF1IHRlc3QgKExqdW5nLUJveCB0ZXN0KSBzaG93cyBhIGhpZ2hlciBwLXZhbHVlIHdoaWNoIG1lYW5zIHRoZSB2YWx1ZXMgYXJlIGluZHBlbmRlbnQuDQppLmUgV2UgZmFpbCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcw0KDQpGaXR0aW5nIHRoZSBBUklNQSBtb2RlbCB3aXRoIHRoZSBleGlzdGluZyBkYXRhDQoNCkxldCdzIHBsb3QgdGhlIGFjdHVhbHMgYWdhaW5zdCB0aGUgZml0dGVkIHZhbHVlcw0KDQoqKkNvbnZlcnQgbW9kZWwgYW5kIHRpbWUgc2VyaWVzIHRvIGRhdGFmcmFtZSBmb3IgcGxvdHRpbmcqKg0KDQpgYGB7cn0NCmRhaWx5X2RlYXRoX2hiX3RpbWVzZXJpZXNfZGF0YSA8LSBmb3J0aWZ5KGRhaWx5X2RlYXRoX2hiX3RpbWVzZXJpZXMpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIHJlbW92ZV9yb3duYW1lcyAlPiUgDQogIHJlbmFtZSAoZGF0ZSA9IGluZGV4LA0KICAgICAgICAgIGRlYXRoID0gZGF0YSklPiUgDQogIG11dGF0ZShpbmRleCA9IHNlcSgxOm5yb3coZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllcykpKQ0KICANCmFyaW1hX2ZpdF9kdGhfcmVzaWQgPC0gdHMoZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllc1sxOm5yb3coZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllcyldKSAtIHJlc2lkKGF1dG9fYXJpbWFfZml0X2RlYXRoKQ0KDQphcmltYV9maXRfZHRoX2RhdGEgPC0gZm9ydGlmeShhcmltYV9maXRfZHRoX3Jlc2lkKSAlPiUgDQogIGNsZWFuX25hbWVzKCkgJT4lIA0KICBtdXRhdGUoZGF0YSA9IHJvdW5kKGRhdGEsMikpJT4lIA0KICBtdXRhdGUgKGRhdGEgPSBpZmVsc2UoZGF0YSA8IDAsMCxkYXRhKSkNCg0KZml0X2V4aXN0aW5nX2R0aF9kYXRhIDwtIGRhaWx5X2RlYXRoX2hiX3RpbWVzZXJpZXNfZGF0YSAlPiUgDQogIGlubmVyX2pvaW4oYXJpbWFfZml0X2R0aF9kYXRhLCBieSA9IGMoImluZGV4IikpDQoNCg0KYGBgDQoNCioqUGxvdHRpbmcgdGhlIHNlcmllcyBhbG9uZyB3aXRoIHRoZSBmaXR0ZWQgdmFsdWVzKioNCg0KYGBge3J9DQoNCmZpdF9leGlzdGluZ19kdGhfcGxvdCA8LSBmaXRfZXhpc3RpbmdfZHRoX2RhdGEgJT4lIA0KICAgbXV0YXRlIChkYXRhID0gaWZlbHNlKGRhdGEgPCAwLDAsZGF0YSkpICU+JSANCiAgbXV0YXRlKGRhdGUgPSBhcy5EYXRlKGRhdGUpKSAlPiUgDQogIGdncGxvdCgpKw0KICBhZXMoeD1kYXRlLCB5ID0gZGVhdGgpKw0KICBnZW9tX2xpbmUoY29sb3IgPSIjNWFiNGFjIikrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpLCBjb2xvdXIgPSAicmVkIiApKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIkRlYXRocyByZXBvcnRlZCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIgLSAleSIgKSsNCiAgZ2d0aXRsZSgiRml0dGluZyB0aGUgQVJJTUEgbW9kZWwgd2l0aCBleGlzdGluZyBkYXRhIikgKw0KICBjb2xvcl90aGVtZSgpDQoNCmdncGxvdGx5KGZpdF9leGlzdGluZ19kdGhfcGxvdCkNCg0KYGBgDQoNCiMjIFN0ZXAgNiBGb3JlY2FzdCB1c2luZyB0aGUgbW9kZWwNCg0KKipEYXRhIFByZXBhcmF0aW9uIDoqKg0KDQpgYGB7cn0NCmZvcmVjYXN0X2R0aF9tb2RlbCA8LSBmb3JlY2FzdChhdXRvX2FyaW1hX2ZpdF9kZWF0aCxsZXZlbCA9IGMoODAsIDk1KSwgaCA9IDMwKSANCg0KI0NvbnZlcnQgdGhlIG1vZGVsIHRvIGRhdGFmcmFtZSBmb3IgcGxvdHRpbmcNCg0KIyBOZWdhdGl2ZSB2YWx1ZXMgb2YgdGhlIENJIGludGVydmFsIGFyZSBjb25zaWRlcmVkIGFzIDANCg0KZm9yZWNhc3RfZHRoX21vZGVsX2RhdGEgPC0gZm9ydGlmeShmb3JlY2FzdF9kdGhfbW9kZWwpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIG11dGF0ZShkYXRhID0gcm91bmQoZGF0YSwyKSwNCiAgICAgICAgIGZpdHRlZD0gcm91bmQoZml0dGVkLDIpKSAgJT4lIA0KICBtdXRhdGUgKGxvXzgwID0gaWZlbHNlKGxvXzgwIDwgMCwwLGxvXzgwKSwNCiAgICAgICAgICBsb185NSA9IGlmZWxzZShsb185NSA8IDAsMCxsb185NSkNCiAgICAgICAgICApDQoNCmZvcmVjYXN0X3N0YXJ0X2RhdGUgPC0gYXMuRGF0ZShtYXgoZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllc19kYXRhJGRhdGUpKzEpDQpmb3JlY2FzdF9lbmRfZGF0ZSA8LSBhcy5EYXRlKGZvcmVjYXN0X3N0YXJ0X2RhdGUrMjkpDQoNCmZvcmVjYXN0X2R0aF9kYXRhIDwtIGZvcmVjYXN0X2R0aF9tb2RlbF9kYXRhICU+JSANCiAgZmlsdGVyKCEoaXMubmEocG9pbnRfZm9yZWNhc3QpKSkgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IHNlcShmb3JlY2FzdF9zdGFydF9kYXRlLGZvcmVjYXN0X2VuZF9kYXRlLCBieSA9MSkpICU+JSANCnNlbGVjdCgtZGF0YSwtZml0dGVkLCAtaW5kZXgpICANCg0KZml0dGVkX2R0aF9kYXRhIDwtIGZvcmVjYXN0X2R0aF9tb2RlbF9kYXRhICU+JSANCiAgZmlsdGVyKCEoaXMubmEoZGF0YSkpKSAlPiUgDQogIGlubmVyX2pvaW4oZGFpbHlfZGVhdGhfaGJfdGltZXNlcmllc19kYXRhLCBieSA9IGMoImluZGV4IikpICU+JSANCiAgbXV0YXRlKGRhdGUgPSBhcy5EYXRlKGRhdGUpKSAlPiUgDQpzZWxlY3QoZGF0ZSwgZGF0YSwgZml0dGVkKSANCg0KYGBgDQoNCioqUGxvdHRpbmcgdGhlIFZhY2NpbmF0aW9uIHNlcmllcyBwbHVzIHRoZSBmb3JlY2FzdCBhbmQgODAgLSA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMqKg0KDQpgYGB7cn0NCg0KYW5ub3RhdGlvbiA8LSBkYXRhLmZyYW1lKA0KICAgeCA9IGMoYXMuRGF0ZSgiMTMtMDYtMjAyMSIsIiVkLSVtLSVZIiksYXMuRGF0ZSgiMTEtMTAtMjAyMSIsIiVkLSVtLSVZIikpLA0KICAgeSA9IGMoMzAsNDApLA0KICAgbGFiZWwgPSBjKCJQQVNUIiwgIkZVVFVSRSIpDQopDQoNCiNUaW1lIHNlcmllcyBwbG90cyBmb3IgdGhlIG5leHQgNjAgZGF5cyBhY2NvcmRpbmcgdG8gYmVzdCBBUklNQSBtb2RlbHMgd2l0aCA4MCXigJM5NSUgQ0kuDQpmb3JlY2FzdF9kYXRhX2R0aF9wbG90IDwtIGZpdHRlZF9kdGhfZGF0YSAlPiUgDQogIGdncGxvdCgpKw0KICBnZW9tX2xpbmUoYWVzKHg9IGRhdGUsIHkgPSBkYXRhKSwgY29sb3IgPSAiIzVhYjRhYyIpKw0KICAjZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID0gZml0dGVkKSwgY29sb3VyID0gInJlZCIgKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID1wb2ludF9mb3JlY2FzdCksIGNvbG9yID0iYmx1ZSIsIGRhdGEgPSBmb3JlY2FzdF9kdGhfZGF0YSApKw0KICBnZW9tX3JpYmJvbihhZXMoeCA9IGRhdGUsIHkgPSBwb2ludF9mb3JlY2FzdCwgeW1pbiA9IGxvXzgwLCB5bWF4ID0gaGlfODApLCANCiAgICAgICAgICAgICAgZGF0YSA9IGZvcmVjYXN0X2R0aF9kYXRhLCBhbHBoYSA9IDAuMywgZmlsbCA9ICJncmVlbiIpKw0KICBnZW9tX3JpYmJvbihhZXMoeCA9IGRhdGUsIHkgPSBwb2ludF9mb3JlY2FzdCwgeW1pbiA9IGxvXzk1LCB5bWF4ID0gaGlfOTUpLCANCiAgICAgICAgICAgICAgZGF0YSA9IGZvcmVjYXN0X2R0aF9kYXRhLCBhbHBoYSA9IDAuMSkrDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhtYXgoZGF0ZSkpKSxjb2xvcj0iI2YxYTM0MCIsIGxpbmV0eXBlPSJkYXNoZWQiLGRhdGEgPSBmaXR0ZWRfZHRoX2RhdGEpKw0KICBnZ3RpdGxlKCJQcm9qZWN0aW9uIG9mIG5ldyBEZWF0aHMiKSArDQogIHhsYWIoIk1vbnRoIikgKyANCiAgeWxhYigiRGVhdGggcmVwb3J0ZWQiKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkrDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIGdlb21fdGV4dChkYXRhPWFubm90YXRpb24sIA0KICAgICAgICAgICAgYWVzKCB4PXgsIHk9eSwgbGFiZWw9bGFiZWwpLCAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgY29sb3I9ImJsdWUiLCANCiAgICAgICAgICAgIHNpemU9NCApDQogICANCg0KZ2dwbG90bHkoZm9yZWNhc3RfZGF0YV9kdGhfcGxvdCApDQpgYGANCg==